[open-ils-commits] [GIT] Evergreen ILS branch user/dbs/social created. 99999c3f682802ca44a32218caa4537dc3f3aec8

Evergreen Git git at git.evergreen-ils.org
Wed May 18 10:48:34 EDT 2011


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, user/dbs/social has been created
        at  99999c3f682802ca44a32218caa4537dc3f3aec8 (commit)

- Log -----------------------------------------------------------------
commit 99999c3f682802ca44a32218caa4537dc3f3aec8
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Wed Apr 20 23:57:57 2011 -0400

    Merge social patches from Fall 2010
    
    Nice to have something current to look at, despite all of the
    work that remains to be done.
    
    Signed-off-by: Dan Scott <dan at coffeecode.net>

diff --git a/Open-ILS/examples/apache/eg_vhost.conf b/Open-ILS/examples/apache/eg_vhost.conf
index 7fea97c..1f9437a 100644
--- a/Open-ILS/examples/apache/eg_vhost.conf
+++ b/Open-ILS/examples/apache/eg_vhost.conf
@@ -263,6 +263,17 @@ RewriteRule . - [E=locale:en-US]
 </Location>
 
 # ----------------------------------------------------------------------------------
+# Social intelligence interface
+# ----------------------------------------------------------------------------------
+<Location /opac/social/>
+    SetHandler perl-script
+    PerlHandler OpenILS::WWW::Social
+    Options +ExecCGI
+    PerlSendHeader On
+    allow from all
+</Location>
+
+# ----------------------------------------------------------------------------------
 # Supercat feeds
 # ----------------------------------------------------------------------------------
 <Location /opac/extras/oisbn>
diff --git a/Open-ILS/examples/apache/startup.pl b/Open-ILS/examples/apache/startup.pl
index f828447..03e1806 100755
--- a/Open-ILS/examples/apache/startup.pl
+++ b/Open-ILS/examples/apache/startup.pl
@@ -9,6 +9,7 @@ use OpenILS::WWW::TemplateBatchBibUpdate qw( /openils/conf/opensrf_core.xml );
 use OpenILS::WWW::EGWeb ('/openils/conf/oils_web.xml');
 use OpenILS::WWW::PasswordReset ('/openils/conf/opensrf_core.xml');
 use OpenILS::WWW::IDL2js ('/openils/conf/opensrf_core.xml');
+use OpenILS::WWW::Social ('/openils/conf/opensrf_core.xml');
 
 # - Uncoment the following 2 lines to make use of the IP redirection code
 # - The IP file should to contain a map with the following format:
diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 858150e..150b0ec 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -4507,6 +4507,79 @@ SELECT  usr,
 			<link field="record" reltype="has_a" key="id" map="" class="bre"/>
 		</links>
 	</class>
+	<class id="socrates" controller="open-ils.cstore" oils_obj:fieldmapper="social::user_rating" oils_persist:tablename="social.user_rating" reporter:label="Bib Rating">
+		<fields oils_persist:primary="id" oils_persist:sequence="social.user_rating_id_seq">
+			<field name="create_date" reporter:datatype="timestamp"/>
+			<field name="creator" reporter:datatype="link"/>
+			<field name="edit_date" reporter:datatype="timestamp"/>
+			<field name="id" reporter:datatype="id" />
+			<field name="record" reporter:datatype="link"/>
+			<field name="value"  reporter:datatype="text"/>
+		</fields>
+		<links>
+			<link field="creator" reltype="has_a" key="id" map="" class="au"/>
+			<link field="record" reltype="has_a" key="id" map="" class="bre"/>
+		</links>
+	</class>
+	<class id="socr" controller="open-ils.cstore" oils_obj:fieldmapper="social::user_review" oils_persist:tablename="social.user_review" reporter:label="Bib Review">
+		<fields oils_persist:primary="id" oils_persist:sequence="social.user_review_id_seq">
+			<field name="create_date" reporter:datatype="timestamp"/>
+			<field name="creator" reporter:datatype="link"/>
+			<field name="edit_date" reporter:datatype="timestamp"/>
+			<field name="editor" reporter:datatype="link"/>
+			<field name="approver" reporter:datatype="link"/>
+			<field name="approved" reporter:datatype="bool"/>
+			<field name="id" reporter:datatype="id" />
+			<field name="record" reporter:datatype="link"/>
+			<field name="value"  reporter:datatype="text"/>
+		</fields>
+		<links>
+			<link field="creator" reltype="has_a" key="id" map="" class="au"/>
+			<link field="editor" reltype="has_a" key="id" map="" class="au"/>
+			<link field="approver" reltype="has_a" key="id" map="" class="au"/>
+			<link field="record" reltype="has_a" key="id" map="" class="bre"/>
+		</links>
+	</class>
+	<class id="soct" controller="open-ils.cstore" oils_obj:fieldmapper="social::tag" oils_persist:tablename="social.tag" reporter:label="Bib Tag">
+		<fields oils_persist:primary="id" oils_persist:sequence="social.tag_id_seq">
+			<field name="edit_date" reporter:datatype="timestamp"/>
+			<field name="editor" reporter:datatype="link"/>
+			<field name="approver" reporter:datatype="link"/>
+			<field name="approved" reporter:datatype="bool"/>
+			<field name="id" reporter:datatype="id" />
+			<field name="value"  reporter:datatype="text"/>
+		</fields>
+		<links>
+			<link field="editor" reltype="has_a" key="id" map="" class="au"/>
+			<link field="approver" reltype="has_a" key="id" map="" class="au"/>
+		</links>
+	</class>
+	<class id="socbtm" controller="open-ils.cstore" oils_obj:fieldmapper="social::biblio_tag_map" oils_persist:tablename="social.biblio_tag_map" reporter:label="Bib Tag Map">
+		<fields oils_persist:primary="id" oils_persist:sequence="biblio.tag_id_seq">
+			<field name="create_date" reporter:datatype="timestamp"/>
+			<field name="creator" reporter:datatype="link"/>
+			<field name="id" reporter:datatype="id" />
+			<field name="record" reporter:datatype="link"/>
+			<field name="tag"  reporter:datatype="link"/>
+		</fields>
+		<links>
+			<link field="creator" reltype="has_a" key="id" map="" class="au"/>
+			<link field="record" reltype="has_a" key="id" map="" class="bre"/>
+			<link field="tag" reltype="has_a" key="id" map="" class="soct"/>
+		</links>
+	</class>
+	<class id="socas" controller="open-ils.cstore" oils_obj:fieldmapper="social::activity_stream" reporter:label="Social Activity Stream" oils_persist:readonly="true">
+		<fields>
+			<field reporter:label="Actor" name="actor" reporter:datatype="id"/>
+			<field reporter:label="Object" name="object" reporter:datatype="int"/>
+			<field reporter:label="Target" name="target" reporter:datatype="int"/>
+			<field reporter:label="Activity Time" name="stamped" reporter:datatype="timestamp"/>
+			<field reporter:label="Activity" name="activity" reporter:datatype="text"/>
+		</fields>
+		<links>
+			<link field="actor" reltype="has_a" key="id" map="" class="au"/>
+		</links>
+	</class>
 	<class id="mucs" controller="open-ils.cstore" oils_obj:fieldmapper="money::user_circulation_summary" oils_persist:tablename="money.usr_circulation_summary" reporter:label="User Circulation Summary">
 		<fields oils_persist:primary="usr" oils_persist:sequence="">
 			<field name="balance_owed" reporter:datatype="money" />
diff --git a/Open-ILS/src/Makefile.am b/Open-ILS/src/Makefile.am
index 7d52ad5..2cce0ba 100644
--- a/Open-ILS/src/Makefile.am
+++ b/Open-ILS/src/Makefile.am
@@ -170,6 +170,7 @@ ilscore-install:
 	$(MKDIR_P) $(TEMPLATEDIR)
 	cp -r @srcdir@/templates/marc $(TEMPLATEDIR)
 	cp -r @srcdir@/templates/password-reset $(TEMPLATEDIR)
+	cp -r @srcdir@/templates/social $(TEMPLATEDIR)
 	@echo "Installing string templates to $(TEMPLATEDIR)"
 	$(MKDIR_P) $(TEMPLATEDIR)
 	$(MKDIR_P) $(datadir)/overdue/
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/biblio.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/biblio.pm
index 7780cbc..f9cbd22 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/biblio.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/biblio.pm
@@ -20,7 +20,6 @@ use base qw/biblio/;
 biblio::record_note->table( 'biblio_record_note' );
 biblio::record_note->columns( Essential => qw/id record value creator
 					editor create_date edit_date pub/ );
-#-------------------------------------------------------------------------------
 
 #-------------------------------------------------------------------------------
 package biblio::peer_type;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/social.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/social.pm
new file mode 100644
index 0000000..7a105f4
--- /dev/null
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/CDBI/social.pm
@@ -0,0 +1,45 @@
+package OpenILS::Application::Storage::CDBI::social;
+our $VERSION = 1;
+
+#-------------------------------------------------------------------------------
+package social;
+use base qw/OpenILS::Application::Storage::CDBI/;
+#-------------------------------------------------------------------------------
+package social::user_rating;
+use base qw/social/;
+
+social::user_rating->table('social_user_rating');
+social::user_rating->columns (Essential => qw/id record value creator
+   create_date edit_date/);
+
+#-------------------------------------------------------------------------------
+package social::user_review;
+use base qw/social/;
+
+social::user_review->table('social_user_review');
+social::user_review->columns (Essential => qw/id record value creator
+   approver approved create_date edit_date/);
+
+#-------------------------------------------------------------------------------
+package social::tag;
+use base qw/social/;
+
+social::tag->table('social_tag');
+social::tag->columns (Essential => qw/id value approver approved editor
+    edit_date/);
+
+#-------------------------------------------------------------------------------
+package social::biblio_tag_map;
+use base qw/social/;
+
+social::biblio_tag_map->table('social_user_tag');
+social::biblio_tag_map->columns (Essential => qw/id record tag creator create_date /);
+
+#-------------------------------------------------------------------------------
+package social::activity_stream;
+use base qw/social/;
+
+social::biblio_tag_map->table('social_activity_stream');
+social::biblio_tag_map->columns (Essential => qw/actor object target stamped activity/);
+
+1;
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/Social.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/Social.pm
new file mode 100644
index 0000000..6e8f621
--- /dev/null
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/Social.pm
@@ -0,0 +1,793 @@
+package OpenILS::WWW::Social;
+
+# Copyright (C) 2010 Laurentian University
+# Dan Scott <dscott at laurentian.ca>
+# 
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict; use warnings;
+
+use Apache2::Log;
+use Apache2::Const -compile => qw(OK REDIRECT DECLINED NOT_FOUND :log);
+use APR::Const    -compile => qw(:error SUCCESS);
+use Apache2::RequestRec ();
+use Apache2::RequestIO ();
+use Apache2::RequestUtil;
+use CGI;
+use Template;
+
+use OpenSRF::EX qw(:try);
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::Cache;
+use OpenSRF::System;
+use OpenSRF::AppSession;
+
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenILS::Application::AppUtils;
+use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use OpenILS::Utils::ModsParser;
+use XML::LibXML;
+use DateTime;
+
+my $log = 'OpenSRF::Utils::Logger';
+my $U = 'OpenILS::Application::AppUtils';
+
+my ($bootstrap, $editor, $actor, $templates, $LocalTZ);
+my $i18n = {};
+my $init_done = 0; # has child_init been called?
+
+# helper functions inserted into the TT environment
+my $_TT_helpers = {
+
+    # turns a date into something TT can understand
+    format_date => sub {
+        my $date = shift;
+        $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date));
+        return sprintf(
+            "%0.2d:%0.2d:%0.2d %0.2d-%0.2d-%0.4d",
+            $date->hour,
+            $date->minute,
+            $date->second,
+            $date->day,
+            $date->month,
+            $date->year
+        );
+    },
+
+    # escapes a string for inclusion in an XML document.  escapes &, <, and > characters
+    escape_xml => sub {
+        my $str = shift;
+        $str =~ s/&/&amp;/sog;
+        $str =~ s/</&lt;/sog;
+        $str =~ s/>/&gt;/sog;
+        return $str;
+    },
+
+};
+
+sub import {
+    my $self = shift;
+    $bootstrap = shift;
+}
+
+sub child_init {
+    OpenSRF::System->bootstrap_client( config_file => $bootstrap );
+    
+    my $conf = OpenSRF::Utils::SettingsClient->new();
+    my $idl = $conf->config_value("IDL");
+    Fieldmapper->import(IDL => $idl);
+    $templates = $conf->config_value("dirs", "templates");
+    OpenILS::Utils::CStoreEditor::init();
+    $editor = new_editor();
+    load_i18n();
+
+   # Getting the timezone is slow; do it once
+    $LocalTZ = DateTime::TimeZone->new( name => 'local' );
+ 
+    $init_done = 1;
+}
+
+sub handler {
+    my $apache = shift;
+
+    child_init() unless $init_done;
+
+    return Apache2::Const::DECLINED if (-e $apache->filename);
+
+    my $ctx = {};
+
+    $ctx->{'helpers'} = $_TT_helpers;
+
+    $ctx->{'uri'} = $apache->uri;
+
+    # Get our locale from the URL
+    (my $locale = $apache->path_info) =~ s{^.*?/([a-z]{2}-[A-Z]{2})/.*?$}{$1};
+    if (!$locale) {
+        $locale = 'en-US';
+    }
+
+    # If locale exists, use it; otherwise fall back to en-US
+    if (exists $i18n->{$locale}) {
+        $ctx->{'i18n'} = $i18n->{$locale};
+    } else {
+        $ctx->{'i18n'} = $i18n->{'en-US'};
+    }
+
+    my $tt = Template->new({
+        INCLUDE_PATH => $templates
+    }) || die "$Template::ERROR\n";
+
+    # So what object are we dealing with here?
+    if ($apache->uri =~ m{/social(/[a-z]{2}-[A-Z]{2})?/user/}) {
+        if ($apache->uri =~ m{.atom$}) {
+            return user_activity_stream($apache);
+        } else {
+            return display_user($apache, $tt, $ctx);
+        }
+    } elsif ($apache->uri =~ m{/social(/[a-z]{2}-[A-Z]{2})?/thing/}) {
+        # A "thing" is represented by a biblio.record_entry
+        # It is not necessarily a book, thus "thing"
+        if ($apache->uri =~ m{.atom$}) {
+            return thing_activity_stream($apache);
+        } else {
+            return display_thing($apache, $tt, $ctx);
+        }
+    }
+}
+
+# XXX Need to build an HTML-friendly template for displaying a thing
+# and all of the social activity around it
+sub display_thing {
+    return;
+}
+
+# The user URIs identify the user by ID because usernames and email can change
+# Grab the user object from the database while we're at it
+sub _get_base_user {
+    my $apache = shift;
+
+    (my $uid = $apache->uri) =~ s{.*/social(/[a-z]{2}-[A-Z]{2})?/user/(\d+).*?$}{$2};
+
+    my $user = $editor->retrieve_actor_user($uid);
+
+    return $user;
+}
+
+# Things are identified strictly by their record ID
+sub thing_activity_stream {
+    my ($apache) = @_;
+
+    (my $thing = $apache->uri) =~ s{.*/social(/[a-z]{2}-[A-Z]{2})?/thing/(\d+).*?$}{$2};
+
+    if (!$thing) {
+        return Apache2::Const::OK;
+    }
+
+    $apache->content_type('application/atom+xml');
+
+    my $stream = create_thing_feed($apache, $thing);
+
+    print $stream->toString(1);
+    
+    return Apache2::Const::OK;
+}
+
+# Generate the Activity Stream for a given user
+sub user_activity_stream {
+    my ($apache) = @_;
+
+    my $user = _get_base_user($apache);
+    if (!$user) {
+        return Apache2::Const::OK;
+    }
+
+    $apache->content_type('application/atom+xml');
+
+    my $stream = create_user_feed($apache, $user);
+    print $stream->toString(1);
+    
+    return Apache2::Const::OK;
+}
+
+# Generate the Activity Stream for a given thing
+sub create_thing_feed {
+    my ($apache, $thing) = @_;
+
+    my ($doc, $feed) = base_feed($apache);
+
+    my $title = $doc->createElementNS(undef, "title");
+    $title->appendTextNode('Activity stream for the object "' . get_bib_and_mods($thing)->title . '"');
+    $feed->addChild($title);
+
+    create_thing_entries($apache, $doc, $thing);
+
+    return $doc;
+}
+
+# All Activity Stream feeds have a common base
+# XXX We're assuming HTTP, not HTTPS here...
+sub base_feed {
+    my ($apache) = @_;
+    
+    my $now = DateTime->now(time_zone => $LocalTZ);
+
+    my $host = $apache->hostname;
+    my $uri = $apache->uri;
+    my $html_uri = $uri;
+    $html_uri =~ s/.atom$//;
+
+    my $doc = XML::LibXML->createDocument();
+    my $feed = $doc->createElementNS("http://www.w3.org/2005/Atom", "feed");
+    $doc->setDocumentElement($feed);
+
+    $feed->setNamespace("http://www.w3.org/2005/Atom");
+    $feed->setNamespace("http://activitystrea.ms/spec/1.0/", "activity", 0);
+    $feed->setNamespace("http://portablecontacts.net/spec/1.0", "poco", 0);
+
+    my $feed_id = $doc->createElementNS(undef, "id");
+    $feed_id->appendTextNode(generate_tag($host, $uri, $now));
+    $feed->addChild($feed_id);
+
+    my $updated = $doc->createElementNS(undef, "updated");
+    $updated->appendTextNode($now->strftime('%FT%TZ'));
+    $feed->addChild($updated);
+
+    # Link to our HTML representation
+    my $l_html = $doc->createElementNS(undef, "link");
+    $l_html->setAttributeNS(undef, "rel", "alternate");
+    $l_html->setAttributeNS(undef, "type", "text/html");
+    $l_html->setAttributeNS(undef, "href", "http://$host$html_uri");
+    $feed->addChild($l_html);
+
+    # Link to our self
+    my $l_self = $doc->createElementNS(undef, "link");
+    $l_self->setAttributeNS(undef, "rel", "self");
+    $l_self->setAttributeNS(undef, "type", "application/atom+xml");
+    $l_self->setAttributeNS(undef, "href", "http://$host$uri");
+    $feed->addChild($l_self);
+
+    return ($doc, $feed);
+}
+
+# Standardized form of representing the user's full name
+sub generate_user_name {
+    my ($user) = @_;
+
+    my $display_name = $user->first_given_name . ' ';
+    $display_name .= ($user->second_given_name || '') . ' ';
+    $display_name .= $user->family_name || '';
+    $display_name =~ s/ +/ /g;
+
+    return $display_name;
+}
+
+sub create_user_feed {
+    my ($apache, $user) = @_;
+
+    my ($doc, $feed) = base_feed($apache);
+    my $display_name = generate_user_name($user);
+
+    my $title = $doc->createElementNS(undef, "title");
+    $title->appendTextNode("Personal activity stream for " . ($display_name || ("person " . $user->id)));
+    $feed->addChild($title);
+
+    add_author($doc, $user, $feed);
+
+    create_user_entries($apache, $doc, $user, $display_name || $user->usrname);
+
+    return $doc;
+}
+
+sub add_author {
+    my ($doc, $user, $anchor) = @_;
+
+    my $display_name = generate_user_name($user);
+
+    # Introduce the actor (atom:author)
+    my $author = $doc->createElementNS(undef, "author");
+
+    # atom:name link, required in atom:author elements
+    my $aname = $doc->createElementNS(undef, "name");
+    my $aname_done = 0;
+
+    # Get the best name we can; flesh out Portable Contacts while we're at it
+    if ($display_name) {
+        my $dname = $doc->createElementNS(undef, "poco:displayName");
+        $dname->appendTextNode($display_name);
+        $anchor->addChild($dname);
+
+        $aname->appendTextNode($display_name);
+        $author->addChild($aname);
+        $aname_done = 1;
+    }
+
+    my $uname = $doc->createElementNS(undef, "poco:preferredUsername");
+    if ($user->usrname) {
+        $uname->appendTextNode($user->usrname);
+        if (!$aname_done) {
+            $aname->appendTextNode($user->usrname);
+            $author->addChild($aname);
+            $aname_done = 1;
+        }
+    } else {
+        # This is a poor excuse for an author name; oh well
+        $uname->appendTextNode($user->id);
+        if (!$aname_done) {
+            $aname->appendTextNode($user->id);
+        }
+        $author->addChild($aname);
+    }
+    $anchor->addChild($author);
+
+    if ($user->email) {
+        my $email = $doc->createElementNS(undef, "email");
+        $email->appendTextNode($user->email);
+        $author->addChild($email);
+    }
+    $anchor->addChild($uname);
+}
+
+sub create_user_entries {
+    my ($apache, $doc, $user, $user_name) = @_;
+
+    my $activities = $editor->search_social_activity_stream(
+        { actor => $user->id },
+        { order_by => { socas => "stamped DESC" }, limit => 10 }
+    );
+
+    foreach my $activity (@$activities) {
+        generate_activity_entries($activity, $apache, $doc, $user, $user_name);
+    }
+}
+
+sub create_thing_entries {
+    my ($apache, $doc, $thing) = @_;
+
+    my $activities = $editor->search_social_activity_stream(
+        { object => $thing },
+        { order_by => { socas => "stamped DESC" }, limit => 10 }
+    );
+
+    foreach my $activity (@$activities) {
+        my $user = $editor->retrieve_actor_user($activity->actor);
+        my $display_name = generate_user_name($user);
+        generate_activity_entries($activity, $apache, $doc, $user, $display_name || $user->usrname);
+    }
+}
+
+
+sub generate_activity_entries {
+    my ($activity, $apache, $doc, $user, $user_name) = @_;
+
+    my $entry = $doc->createElementNS(undef, "entry");
+
+    if ($activity->activity eq "add_bookbag") {
+        _add_bookbag($activity, $user_name, $apache, $doc, $entry);
+    } elsif ($activity->activity eq "add_bookbag_item") {
+        _add_bookbag_item($activity, $user_name, $apache, $doc, $entry);
+    } elsif ($activity->activity eq "add_review") {
+        _add_review($activity, $user_name, $apache, $doc, $entry);
+    } elsif ($activity->activity eq "circ") {
+        _circ($activity, $user_name, $apache, $doc, $entry);
+    }
+
+    # Required by Atom Activity spec
+    my $published = $doc->createElementNS(undef, "published");
+    my $date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($activity->stamped));
+    $published->appendTextNode($date->strftime('%FT%TZ'));
+    $entry->addChild($published);
+
+    # Required by Atom spec
+    my $updated = $doc->createElementNS(undef, "updated");
+    $updated->appendTextNode($date->strftime('%FT%TZ'));
+    $entry->addChild($updated);
+
+    add_author($doc, $user, $entry);
+
+    $doc->documentElement()->addChild($entry);
+}
+
+sub _add_review {
+    my ($activity, $user_name, $apache, $doc, $entry) = @_;
+    # verb = post (http://activitystrea.ms/schema/1.0/post)
+    # object = list (http://activitystrea.ms/schema/1.0/review)
+
+    my $review = $editor->retrieve_social_user_review($activity->object);
+    my $mods = get_bib_and_mods($activity->target);
+
+    my $title = $doc->createElementNS(undef, "title");
+    $title->appendTextNode("$user_name reviewed " . $mods->title);
+    $entry->addChild($title);
+
+    my $summary = $doc->createElementNS(undef, "summary");
+    $summary->appendTextNode("$user_name reviewed " . $mods->title);
+    $entry->addChild($summary);
+
+    my $content = $doc->createElementNS(undef, "content");
+    $content->setAttributeNS(undef, "type", "text/html");
+    $content->appendTextNode($review->value);
+    $entry->addChild($content);
+
+    my $id = $doc->createElementNS(undef, "id");
+    $id->appendTextNode(generate_tag($apache->hostname, '/opac/social/create/review/' . $activity->object));
+    $entry->addChild($id);
+
+    my $verb = $doc->createElementNS(undef, "activity:verb");
+    $verb->appendTextNode("http://activitystrea.ms/schema/1.0/post");
+    $entry->addChild($verb);
+
+    my $object = $doc->createElementNS(undef, "activity:object");
+    my $object_type = $doc->createElementNS(undef, "activity:object-type");
+    $object_type->appendTextNode("http://activitystrea.ms/schema/1.0/review");
+    $object->addChild($object_type);
+
+    my $object_id =  $doc->createElementNS(undef, "id");
+    $object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/review/' . $activity->object));
+    $object->addChild($object_id);
+
+    my $object_content =  $doc->createElementNS(undef, "content");
+    $object_content->setAttributeNS(undef, "type", "text/html");
+    $object_content->appendTextNode($review->value);
+    $object->addChild($object_content);
+
+    my $object_link =  $doc->createElementNS(undef, "link");
+    $object_link->setAttributeNS(undef, "rel", "alternate");
+    $object_link->setAttributeNS(undef, "type", "text/html");
+    $object_link->setAttributeNS(undef, "href", 'http://' . $apache->hostname . '/opac/social/review/' . $activity->object);
+    $object->addChild($object_link);
+
+    # Must be empty if the review does not have a user-generated title
+    my $object_title = $doc->createElementNS(undef, "title");
+    $object->addChild($object_title);
+    $entry->addChild($object);
+
+    my $target = $doc->createElementNS(undef, "activity:target");
+    my $t_object_type = $doc->createElementNS(undef, "activity:object-type");
+    $t_object_type->appendTextNode("http://activitystrea.ms/schema/1.0/list");
+    $target->addChild($t_object_type);
+
+    my $t_object_id =  $doc->createElementNS(undef, "id");
+    $t_object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/thing/' . $activity->target));
+    $target->addChild($t_object_id);
+
+    my $t_object_title = $doc->createElementNS(undef, "title");
+    $t_object_title->appendTextNode($mods->title);
+    $target->addChild($t_object_title);
+
+    $entry->addChild($target);
+
+}
+
+
+sub _circ {
+    my ($activity, $user_name, $apache, $doc, $entry) = @_;
+    # verb = post (http://activitystrea.ms/schema/1.0/post)
+    # object = list (http://activitystrea.ms/schema/1.0/list)
+
+    my $circ = $editor->retrieve_action_circulation($activity->object);
+    my $mods = get_bib_and_mods($activity->target);
+
+    my $title = $doc->createElementNS(undef, "title");
+    $title->appendTextNode("$user_name returned " . $mods->title);
+    $entry->addChild($title);
+
+    my $summary = $doc->createElementNS(undef, "summary");
+    $summary->appendTextNode("$user_name returned " . $mods->title);
+    $entry->addChild($summary);
+
+    my $content = $doc->createElementNS(undef, "content");
+    $content->appendTextNode("$user_name returned " . $mods->title);
+    $entry->addChild($content);
+
+    my $id = $doc->createElementNS(undef, "id");
+    $id->appendTextNode(generate_tag($apache->hostname, '/opac/social/create/circ/' . $activity->object));
+    $entry->addChild($id);
+
+    my $verb = $doc->createElementNS(undef, "activity:verb");
+    $verb->appendTextNode("http://activitystrea.ms/schema/1.0/post");
+    $entry->addChild($verb);
+
+    my $object = $doc->createElementNS(undef, "activity:object");
+    my $object_type = $doc->createElementNS(undef, "activity:object-type");
+    $object_type->appendTextNode("http://activitystrea.ms/schema/1.0/list");
+    $object->addChild($object_type);
+
+    my $object_id =  $doc->createElementNS(undef, "id");
+    $object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/circ/' . $activity->object));
+    $object->addChild($object_id);
+
+    my $object_title = $doc->createElementNS(undef, "title");
+    $object_title->appendTextNode("Circulation");
+    $object->addChild($object_title);
+    $entry->addChild($object);
+}
+
+
+sub _add_bookbag {
+    my ($activity, $user_name, $apache, $doc, $entry) = @_;
+    # verb = post (http://activitystrea.ms/schema/1.0/post)
+    # object = list (http://activitystrea.ms/schema/1.0/list)
+
+    my $bb = $editor->retrieve_container_biblio_record_entry_bucket($activity->object);
+
+    my $title = $doc->createElementNS(undef, "title");
+    $title->appendTextNode("$user_name created the bookbag " . $bb->name);
+    $entry->addChild($title);
+
+    my $summary = $doc->createElementNS(undef, "summary");
+    $summary->appendTextNode("$user_name created the bookbag " . $bb->name);
+    $entry->addChild($summary);
+
+    my $content = $doc->createElementNS(undef, "content");
+    $content->appendTextNode("$user_name created the bookbag " . $bb->name);
+    $entry->addChild($content);
+
+    my $id = $doc->createElementNS(undef, "id");
+    $id->appendTextNode(generate_tag($apache->hostname, '/opac/social/create/bookbag/' . $activity->object));
+    $entry->addChild($id);
+
+    my $verb = $doc->createElementNS(undef, "activity:verb");
+    $verb->appendTextNode("http://activitystrea.ms/schema/1.0/post");
+    $entry->addChild($verb);
+
+    my $object = $doc->createElementNS(undef, "activity:object");
+    my $object_type = $doc->createElementNS(undef, "activity:object-type");
+    $object_type->appendTextNode("http://activitystrea.ms/schema/1.0/list");
+    $object->addChild($object_type);
+
+    my $object_id =  $doc->createElementNS(undef, "id");
+    $object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/bookbag/' . $activity->object));
+    $object->addChild($object_id);
+
+    my $object_title = $doc->createElementNS(undef, "title");
+    $object_title->appendTextNode($bb->name);
+    $object->addChild($object_title);
+    $entry->addChild($object);
+}
+
+sub _add_bookbag_item {
+    my ($activity, $user_name, $apache, $doc, $entry) = @_;
+    # verb = save (http://activitystrea.ms/schema/1.0/save)
+    # object = article (ugh) (http://activitystrea.ms/schema/1.0/article)
+    # target = list (http://activitystrea.ms/schema/1.0/list) 
+
+    my $bb = $editor->retrieve_container_biblio_record_entry_bucket($activity->target);
+    my $mods = get_bib_and_mods($activity->object);
+
+    my $id = $doc->createElementNS(undef, "id");
+    $id->appendTextNode(generate_tag($apache->hostname, '/opac/social/save/bookbag_item/' . $activity->target . '/' . $activity->object));
+    $entry->addChild($id);
+
+    my $title = $doc->createElementNS(undef, "title");
+    $title->appendTextNode("$user_name added " . $mods->title . " to bookbag " . $bb->name);
+    $entry->addChild($title);
+
+    my $summary = $doc->createElementNS(undef, "summary");
+    $summary->appendTextNode("$user_name added " . $mods->title . " to bookbag " . $bb->name);
+    $entry->addChild($summary);
+
+    my $content = $doc->createElementNS(undef, "content");
+    $content->appendTextNode("$user_name added " . $mods->title . " to bookbag " . $bb->name);
+    $entry->addChild($content);
+
+    my $verb = $doc->createElementNS(undef, "activity:verb");
+    $verb->appendTextNode("http://activitystrea.ms/schema/1.0/save");
+    $entry->addChild($verb);
+
+    my $object = $doc->createElementNS(undef, "activity:object");
+    my $object_type = $doc->createElementNS(undef, "activity:object-type");
+    $object_type->appendTextNode("http://activitystrea.ms/schema/1.0/article");
+    $object->addChild($object_type);
+
+    my $object_id =  $doc->createElementNS(undef, "id");
+    $object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/bookbag/' . $activity->object));
+    $object->addChild($object_id);
+
+    my $object_title = $doc->createElementNS(undef, "title");
+    $object_title->appendTextNode($mods->title);
+    $object->addChild($object_title);
+
+    $entry->addChild($object);
+
+    my $target = $doc->createElementNS(undef, "activity:target");
+    my $t_object_type = $doc->createElementNS(undef, "activity:object-type");
+    $t_object_type->appendTextNode("http://activitystrea.ms/schema/1.0/list");
+    $target->addChild($t_object_type);
+
+    my $t_object_id =  $doc->createElementNS(undef, "id");
+    $t_object_id->appendTextNode(generate_tag($apache->hostname, '/opac/social/thing/' . $activity->target));
+    $target->addChild($t_object_id);
+
+    my $t_object_title = $doc->createElementNS(undef, "title");
+    $t_object_title->appendTextNode($bb->name);
+    $target->addChild($t_object_title);
+
+    $entry->addChild($target);
+}
+
+
+# Generate a unique tag for this feed or feed entry
+sub generate_tag {
+    my ($hostname, $uri, $now) = @_;
+
+    if (!$now) {
+        $now = DateTime->now(time_zone => $LocalTZ);
+    }
+
+    $hostname =~ s{^.*?([^\.]+\.[^\.]{2,4})/?$}{$1};
+    $uri =~ s{#}{/}g;
+    my $date = $now->strftime('%F');
+    return "tag:$hostname,$date:$uri";
+}
+
+=pod
+
+Need to build a $ctx structure for the user info like so:
+    $ctx->{'user'}->{'avatar'}
+    $ctx->{'user'}->{'email'}
+    $ctx->{'user'}->{'name'}
+    $ctx->{'user'}->{'usrname'}
+    $ctx->{'home_library'}->{'name'}
+    $ctx->{'circ_history'}->[ {'isbn', 'record_id', 'title', 'author', 'xact_finish' }, ]
+=cut
+sub display_user {
+    my ($apache, $tt, $ctx) = @_;
+
+    my $user = _get_base_user($apache);
+    if (!$user) {
+        $tt->process('social/user/about.tt2', $ctx) || die $tt->error();
+        return Apache2::Const::OK;
+    }
+
+    # Check user settings here to prevent disclosing information they
+    # do not want to disclose
+
+    $ctx->{'user'}{'usrname'} = $user->usrname;
+    $ctx->{'user'}{'email'} = $user->email || '';
+    $ctx->{'user'}{'name'} = generate_user_name($user);
+
+    my $home_ou = $editor->retrieve_actor_org_unit($user->home_ou);
+    $ctx->{'home_library'}{'name'} = $home_ou->name;
+
+    # Now get circs - expose most recent 10 circs by default?
+    my $circs = $editor->search_action_circulation([
+        { usr => $user->id, xact_finish => { '!=' => undef } },
+        { order_by => { circ => 'xact_start DESC' }, limit => 10 }
+    ]);
+
+    my @circ_history;
+    foreach my $circ (@$circs) {
+        my $title = $U->simple_scalar_request(
+            "open-ils.storage", 
+            "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
+            $circ->target_copy
+        );
+
+		next unless $title;
+
+        my $mods = get_mods($title);
+
+        push @circ_history, {
+            record_id => $title->id,
+            isbn => $mods->isbn,
+            title => $mods->title,
+            author => $mods->author,
+            xact_finish => $circ->xact_finish
+        };
+    }
+
+    if (@circ_history) {
+        $ctx->{'circ_history'} = \@circ_history;
+    }
+
+    # Now expose public bookbags; set a reasonable limit
+    my @bookbags;
+    my $bbs = $editor->search_container_biblio_record_entry_bucket([
+        { owner => $user->id, pub => 't', btype => 'bookbag' },
+        { order_by => { cbreb => 'name ASC' }, limit => 100 }
+    ]);
+
+    foreach my $bb (@$bbs) {
+        my $bbitems = $editor->search_container_biblio_record_entry_bucket_item([
+            { bucket => $bb->id }
+        ]);
+
+        push @bookbags, { id => $bb->id, name => $bb->name, count => (scalar @$bbitems) };
+
+    };
+
+    if (@bookbags) {
+        $ctx->{'bookbags'} = \@bookbags;
+    }
+#        my $bbitems = $editor->search_container_biblio_record_entry_bucket_item([
+#            { bucket => $bb->id },
+#            { order_by => { cbrebi => 'id ASC' }, limit => 100 }
+#        ]);
+#
+#        foreach my $bbitem (@$bbitems) {
+#            my $title = $editor->retrieve_biblio_record_entry($bbitem->target_biblio_record_entry);
+#            get_mini_records($title, $bookbags);
+#        }
+
+    # Now expose published reviews; set a reasonable limit
+    my @reviews;
+    my $revs = $editor->search_social_user_review([
+        { creator => $user->id, approved => 't' },
+        { order_by => { socr => 'create_date DESC' }, limit => 100 }
+    ]);
+
+    foreach my $review (@$revs) {
+        my $mods = get_bib_and_mods($review->record);
+
+        push @reviews, {
+            record_id => $review->record,
+            isbn => $mods->isbn,
+            title => $mods->title,
+            author => $mods->author,
+            review => $review->value
+        };
+    };
+
+    if (@reviews) {
+        $ctx->{'reviews'} = \@reviews;
+    }
+
+    $apache->content_type('text/html');
+
+    $tt->process('social/user/about.tt2', $ctx) || die $tt->error();
+    return Apache2::Const::OK;
+}
+
+sub get_bib_and_mods {
+    my ($bib_id) = @_;
+    
+    my $bre = $editor->retrieve_biblio_record_entry($bib_id);
+
+    my $mods = get_mods($bre);
+}
+
+sub get_mods {
+    my ($bre) = @_;
+
+    my $u = OpenILS::Utils::ModsParser->new();
+    $u->start_mods_batch($bre->marc());
+    my $mods = $u->finish_mods_batch();
+    $mods->doc_id($bre->id) if $mods;
+    my $isbn = $mods->isbn || '';
+    $isbn =~ s/-//g;
+    $isbn =~ s/(\d+).*?$/$1/g;
+    $mods->isbn($isbn);
+
+    return $mods;
+}
+
+# Load our localized strings - lame, need to convert to Locale::Maketext
+sub load_i18n {
+    foreach my $string_bundle (glob("$templates/password-reset/strings.*")) {
+        open(I18NFH, '<', $string_bundle);
+        (my $locale = $string_bundle) =~ s/^.*\.([a-z]{2}-[A-Z]{2})$/$1/;
+        $logger->debug("Loaded locale [$locale] from file: [$string_bundle]");
+        while(<I18NFH>) {
+            my ($string_id, $string) = ($_ =~ m/^(.+?)=(.*?)$/);
+            $i18n->{$locale}{$string_id} = $string;
+        }
+        close(I18NFH);
+    }
+}
+
+1;
+
+# vim: et:ts=4:sw=4
diff --git a/Open-ILS/src/sql/Pg/060.schema.social.sql b/Open-ILS/src/sql/Pg/060.schema.social.sql
new file mode 100644
index 0000000..7e8a29a
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/060.schema.social.sql
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010  Laurentian University
+ * Dan Scott <dscott at laurentian.ca> 
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+DROP SCHEMA IF EXISTS social CASCADE;
+
+BEGIN;
+
+CREATE SCHEMA social;
+
+CREATE TABLE social.user_rating (
+    id          BIGSERIAL   PRIMARY KEY,
+    record      BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
+    value       INT         NOT NULL,
+    creator     INT         NOT NULL REFERENCES actor.usr (id),
+    create_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
+    edit_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
+);
+CREATE INDEX social_user_rating_record_idx ON social.user_rating( record );
+CREATE INDEX social_user_rating_creator_idx ON social.user_rating( creator );
+
+CREATE TABLE social.user_review (
+    id          BIGSERIAL   PRIMARY KEY,
+    record      BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
+    value       TEXT        NOT NULL,
+    creator     INT         NOT NULL REFERENCES actor.usr (id),
+    editor      INT         REFERENCES actor.usr (id),
+    approver    INT         REFERENCES actor.usr (id),
+    approved    BOOL        NOT NULL DEFAULT FALSE,
+    create_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
+    edit_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
+);
+CREATE INDEX social_user_review_approved_idx ON social.user_review( approved );
+CREATE INDEX social_user_review_record_idx ON social.user_review( record );
+CREATE INDEX social_user_review_creator_idx ON social.user_review( creator );
+
+CREATE TABLE social.tag (
+    id          BIGSERIAL   PRIMARY KEY,
+    value       TEXT        NOT NULL,
+    approver    INT         REFERENCES actor.usr (id),
+    approved    BOOL        NOT NULL DEFAULT FALSE,
+    editor      INT         REFERENCES actor.usr (id),
+    edit_date   TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
+);
+CREATE INDEX social_tag_approved_idx ON social.tag( approved );
+
+CREATE TABLE social.biblio_tag_map (
+    id          BIGSERIAL   PRIMARY KEY,
+    record      BIGINT      NOT NULL REFERENCES biblio.record_entry (id),
+    tag         BIGINT      NOT NULL REFERENCES social.tag (id),
+    creator     INT         NOT NULL REFERENCES actor.usr (id),
+    create_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
+);
+CREATE INDEX social_biblio_tag_map_tag_record_idx ON social.biblio_tag_map( record );
+CREATE INDEX social_biblio_tag_map_tag_creator_idx ON social.biblio_tag_map( creator );
+
+CREATE OR REPLACE VIEW social.activity_stream AS
+SELECT actor, object, target, stamped, activity FROM (
+  (
+    SELECT acirc.usr AS "actor", acirc.id AS "object", acn.record AS "target", acirc.xact_finish AS "stamped", 'circ' AS "activity"
+      FROM action.circulation acirc
+        INNER JOIN asset.copy ac ON ac.id = acirc.target_copy
+        INNER JOIN asset.call_number acn ON acn.id = ac.call_number
+      WHERE xact_finish IS NOT NULL
+  )
+  UNION
+  (
+    SELECT creator AS "actor", id AS "object", record AS "target", create_date AS "stamped", 'add_review' AS activity
+      FROM social.user_review
+      WHERE approved IS NOT NULL
+  )
+  UNION
+  (
+    SELECT creator AS "actor", id AS "object", record AS "target", edit_date AS "stamped", 'add_rating' AS activity
+      FROM social.user_rating
+  )
+  UNION
+  (
+      SELECT owner AS "actor", id AS "object", NULL AS "target", create_time, 'add_bookbag' AS "activity"
+        FROM container.biblio_record_entry_bucket
+        WHERE pub IS TRUE
+  )
+  UNION
+  (
+      SELECT cbreb.owner AS "actor", cbrebi.target_biblio_record_entry AS "object", cbrebi.bucket AS "target", cbrebi.create_time, 'add_bookbag_item' AS "activity"
+        FROM container.biblio_record_entry_bucket_item cbrebi
+          INNER JOIN container.biblio_record_entry_bucket cbreb ON cbrebi.bucket = cbreb.id
+        WHERE cbreb.pub IS TRUE
+  )
+) AS activities
+ORDER BY 4 DESC;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/sql_file_manifest b/Open-ILS/src/sql/Pg/sql_file_manifest
index 70d7dcd..8b0d4f4 100644
--- a/Open-ILS/src/sql/Pg/sql_file_manifest
+++ b/Open-ILS/src/sql/Pg/sql_file_manifest
@@ -23,6 +23,7 @@ FTS_CONFIG_FILE
 020.schema.functions.sql
 030.schema.metabib.sql
 040.schema.asset.sql
+060.schema.social.sql
 070.schema.container.sql
 080.schema.money.sql
 090.schema.action.sql
diff --git a/Open-ILS/src/templates/social/user/about.tt2 b/Open-ILS/src/templates/social/user/about.tt2
new file mode 100644
index 0000000..cf07727
--- /dev/null
+++ b/Open-ILS/src/templates/social/user/about.tt2
@@ -0,0 +1,103 @@
+[%- USE date -%]
+<html>
+<head>
+  <style type='text/css'>
+    body, * { font-family: Verdana, Arial, sans-serif; }
+    td.category { font-weight: bold; }
+    td.author, td.title { text-align: center;}
+    table.circ, table.circ th, table.circ td, table.review, table.review th, table.review td {
+        border: solid thin black;
+        border-collapse: collapse;
+        margin: 0em;
+        padding-right: 1em;
+        padding-left: 1em;
+    }
+    img { border-style: none; }
+  </style>
+[% UNLESS user.usrname %]
+  <title>User not found or not public</title>
+</head>
+<body>
+  <h1>User not found or not public</h1>
+</body>
+</html>
+[% ELSE %]
+  <title>About [% helpers.escape_xml(user.usrname) %]</title>
+</head>
+<body>
+  <h1>About [% helpers.escape_xml(user.usrname) %]</h1>
+  <h2>Personal</h2>
+  <table><tbody>
+  [% IF user.avatar %]
+    <img src="[% user.avatar %]" class="avatar" />
+  [% END %]
+  [% IF user.name %]
+  <tr id='user.name'><td class='category'>Name</td><td class='data'>[% helpers.escape_xml(user.name) %]</td></tr>
+  [% END %]
+  [% IF user.email %]
+  <tr id='user.email'><td class='category'>Email</td><td class='data'>[% helpers.escape_xml(user.email) %]</td></tr>
+  [% END %]
+  [% IF home_library %]
+  <tr id='home_ou'><td class='category'>Home library</td><td class='data'>[% helpers.escape_xml(home_library.name) %]</td></tr>
+  [% END %]
+  </tbody></table>
+  <h2>Circulation history</h2>
+  [% IF circ_history %]
+    <table class="circ">
+      <thead>
+        <tr><th>Cover</th><th>Title</th><th>Author</th><th>Date returned</th></tr>
+      </thead>
+      <tbody>
+      [% FOREACH circ = circ_history %]
+        <tr>
+          <td><img src="/opac/extras/ac/jacket/small/[% circ.isbn %]" /></td>
+          <td class="title"><a href="../thing/[% circ.record_id %]">[% helpers.escape_xml(circ.title) %]</a></td>
+          <td class="author">[% helpers.escape_xml(circ.author) %]</td>
+          <td class="checkin">[% date.format(helpers.format_date(circ.xact_finish), '%Y-%m-%d') %]</td></tr>
+      [% END %]
+      </tbody>
+    </table>
+  [% ELSE %]
+    <div>No circulation history available for this user</div>
+  [% END %]
+  <h2>Lists</h2>
+  [% IF bookbags %]
+    <ul>
+      [% FOREACH bb = bookbags %]
+        <li><a href="/opac/extras/feed/bookbag/html-full/[% bb.id %]">[% helpers.escape_xml(bb.name) %]</a>
+            <a href="/opac/extras/feed/bookbag/rss2-full/[% bb.id %]"><img src="/opac/images/small-rss.png" /></a>
+            ([% bb.count %] items)
+        </li>
+      [% END %]
+    </ul>
+  [% ELSE %]
+    <div>This user has shared no lists</div>
+  [% END %]
+  <h2>Reviews written by this user</h2>
+  [% IF reviews %]
+    <table class="review">
+      <thead>
+        <tr><th>Cover</th><th>Title</th><th>Author</th><th>Review</th></tr>
+      </thead>
+      <tbody>
+      [% FOREACH review = reviews %]
+        <tr>
+          <td><img src="/opac/extras/ac/jacket/small/[% review.isbn %]" /></td>
+          <td class="title"><a href="../thing/[% helpers.escape_xml(review.record_id) %]">[% review.title %]</a></td>
+          <td class="author">[% helpers.escape_xml(review.author) %]</td>
+          <td class="checkin">[% helpers.escape_xml(review.review) %]</td></tr>
+      [% END %]
+      </tbody>
+    </table>
+  [% ELSE %]
+    <div>This user has not written any approved reviews</div>
+  [% END %]
+</body>
+</html>
+[% END %]
+<!--
+  * Show links to other social networks (via rel="me" as well as publicly)
+  * Show ratings
+  * Show tags
+  * Link to activity stream
+-->

commit 8b6529ae26255d6f3047284bf99abd1750d23331
Merge: b23949b f69e07f
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Wed Apr 20 23:49:20 2011 -0400

    Merge branch 'master' of git://git.esilibrary.com/git/evergreen-equinox


commit b23949bdaade4904d0e27d7a62a1b5adcac21b0a
Merge: 2ee6568 0762a08
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Mon Apr 11 13:48:36 2011 -0400

    Merge branch 'master' of git://git.esilibrary.com/git/evergreen-equinox


commit 2ee65685ea93c53c85352df0ccb1b65bb0e72e97
Merge: a6b56fd 600dadd
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Fri Apr 8 14:05:44 2011 -0400

    Merge branch 'master' of git://git.esilibrary.com/git/evergreen-equinox


commit a6b56fdd2df2c8137042a5a867bffabbee07e2ff
Merge: f97dc14 26160d2
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Thu Apr 7 18:18:15 2011 -0400

    Merge branch 'master' of git://git.esilibrary.com/git/evergreen-equinox


commit f97dc14ce18aaacd59092f11da36779409657502
Merge: 69da90b e4328f4
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Tue Apr 5 23:33:00 2011 -0400

    Merge branch 'master' of git://git.esilibrary.com/git/evergreen-equinox


commit 69da90b92b54245a643f5301f4e6665748885385
Merge: 8848ac4 f6f578d
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Tue Apr 5 20:39:22 2011 -0400

    Merge branch 'master' of git://git.esilibrary.com/git/evergreen-equinox


commit 8848ac46fdab85f2c32868522a90695ed43ff09c
Merge: d1fa825 cce75ff
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Mon Apr 4 22:03:26 2011 -0400

    Merge branch 'master' of git://git.esilibrary.com/git/evergreen-equinox


commit d1fa8256e8145167a2029ba1fbf2256e07ac002b
Merge: a84a008 1a0442b
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Tue Mar 29 20:58:31 2011 -0400

    Merge branch 'master' of git://git.esilibrary.com/git/evergreen-equinox


commit a84a00882005a96ec6a89fe78e2895f7f467339a
Merge: 4ce43c4 b35bd6b
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Tue Mar 29 11:01:17 2011 -0400

    Merge branch 'master' of git://git.esilibrary.com/git/evergreen-equinox


commit 4ce43c419fd4a607b2bcf6200d559af06a77b11a
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Thu Mar 3 00:27:12 2011 -0500

    Revert "Whitespace consistency for SuperCat.pm"
    
    This reverts commit ea807c1f535a12725f82038bd823d88c0c28a97d.

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
index f473ee8..ae20208 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
@@ -37,115 +37,115 @@ my $U = 'OpenILS::Application::AppUtils';
 my ($bootstrap, $supercat, $actor, $parser, $search, $xslt, $cn_browse_xslt, %browse_types);
 
 $browse_types{call_number}{xml} = sub {
-    my $tree = shift;
+	my $tree = shift;
 
-    my $year = (gmtime())[5] + 1900;
-    my $content = '';
+	my $year = (gmtime())[5] + 1900;
+	my $content = '';
 
-    $content .= "<volumes  xmlns='http://open-ils.org/spec/holdings/v1'>\n";
+	$content .= "<volumes  xmlns='http://open-ils.org/spec/holdings/v1'>\n";
 
-    for my $cn (@$tree) {
-        (my $cn_class = $cn->class_name) =~ s/::/-/gso;
-        $cn_class =~ s/Fieldmapper-//gso;
+	for my $cn (@$tree) {
+		(my $cn_class = $cn->class_name) =~ s/::/-/gso;
+		$cn_class =~ s/Fieldmapper-//gso;
 
-        my $cn_tag = "tag:open-ils.org,$year:$cn_class/".$cn->id;
-        my $cn_lib = $cn->owning_lib->shortname;
-        my $cn_label = $cn->label;
+		my $cn_tag = "tag:open-ils.org,$year:$cn_class/".$cn->id;
+		my $cn_lib = $cn->owning_lib->shortname;
+		my $cn_label = $cn->label;
 
-        $cn_label =~ s/\n//gos;
-        $cn_label =~ s/&/&amp;/go;
-        $cn_label =~ s/'/&apos;/go;
-        $cn_label =~ s/</&lt;/go;
-        $cn_label =~ s/>/&gt;/go;
+		$cn_label =~ s/\n//gos;
+		$cn_label =~ s/&/&amp;/go;
+		$cn_label =~ s/'/&apos;/go;
+		$cn_label =~ s/</&lt;/go;
+		$cn_label =~ s/>/&gt;/go;
 
-        (my $ou_class = $cn->owning_lib->class_name) =~ s/::/-/gso;
-        $ou_class =~ s/Fieldmapper-//gso;
+		(my $ou_class = $cn->owning_lib->class_name) =~ s/::/-/gso;
+		$ou_class =~ s/Fieldmapper-//gso;
 
-        my $ou_tag = "tag:open-ils.org,$year:$ou_class/".$cn->owning_lib->id;
-        my $ou_name = $cn->owning_lib->name;
+		my $ou_tag = "tag:open-ils.org,$year:$ou_class/".$cn->owning_lib->id;
+		my $ou_name = $cn->owning_lib->name;
 
-        $ou_name =~ s/\n//gos;
-        $ou_name =~ s/'/&apos;/go;
+		$ou_name =~ s/\n//gos;
+		$ou_name =~ s/'/&apos;/go;
 
-        (my $rec_class = $cn->record->class_name) =~ s/::/-/gso;
-        $rec_class =~ s/Fieldmapper-//gso;
+		(my $rec_class = $cn->record->class_name) =~ s/::/-/gso;
+		$rec_class =~ s/Fieldmapper-//gso;
 
-        my $rec_tag = "tag:open-ils.org,$year:$rec_class/".$cn->record->id.'/'.$cn->owning_lib->shortname;
+		my $rec_tag = "tag:open-ils.org,$year:$rec_class/".$cn->record->id.'/'.$cn->owning_lib->shortname;
 
-        $content .= "<volume id='$cn_tag' lib='$cn_lib' label='$cn_label'>\n";
-        $content .= "<owning_lib xmlns='http://open-ils.org/spec/actors/v1' id='$ou_tag' name='$ou_name'/>\n";
+		$content .= "<volume id='$cn_tag' lib='$cn_lib' label='$cn_label'>\n";
+		$content .= "<owning_lib xmlns='http://open-ils.org/spec/actors/v1' id='$ou_tag' name='$ou_name'/>\n";
 
-        my $r_doc = $parser->parse_string($cn->record->marc);
-        $r_doc->documentElement->setAttribute( id => $rec_tag );
-        $content .= $U->entityize($r_doc->documentElement->toString);
+		my $r_doc = $parser->parse_string($cn->record->marc);
+		$r_doc->documentElement->setAttribute( id => $rec_tag );
+		$content .= $U->entityize($r_doc->documentElement->toString);
 
-        $content .= "</volume>\n";
-    }
+		$content .= "</volume>\n";
+	}
 
-    $content .= "</volumes>\n";
-    return ("Content-type: application/xml\n\n",$content);
+	$content .= "</volumes>\n";
+	return ("Content-type: application/xml\n\n",$content);
 };
 
 
 $browse_types{call_number}{html} = sub {
-    my $tree = shift;
-    my $p = shift;
-    my $n = shift;
-
-    if (!$cn_browse_xslt) {
-        $cn_browse_xslt = $parser->parse_file(
-                OpenSRF::Utils::SettingsClient
-                        ->new
-                        ->config_value( dirs => 'xsl' ).
-                "/CNBrowse2HTML.xsl"
-        );
-        $cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
-    }
-
-    my (undef,$xml) = $browse_types{call_number}{xml}->($tree);
-
-    return (
-        "Content-type: text/html\n\n",
-        $U->entityize(
-            $cn_browse_xslt->transform(
-                $parser->parse_string( $xml ),
-                'prev' => "'$p'",
-                'next' => "'$n'"
-            )->toString(1)
-        )
-    );
+	my $tree = shift;
+	my $p = shift;
+	my $n = shift;
+
+	if (!$cn_browse_xslt) {
+	        $cn_browse_xslt = $parser->parse_file(
+        	        OpenSRF::Utils::SettingsClient
+                	        ->new
+                        	->config_value( dirs => 'xsl' ).
+	                "/CNBrowse2HTML.xsl"
+        	);
+		$cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
+	}
+
+	my (undef,$xml) = $browse_types{call_number}{xml}->($tree);
+
+	return (
+		"Content-type: text/html\n\n",
+		$U->entityize(
+			$cn_browse_xslt->transform(
+				$parser->parse_string( $xml ),
+				'prev' => "'$p'",
+				'next' => "'$n'"
+			)->toString(1)
+		)
+	);
 };
 
 sub import {
-    my $self = shift;
-    $bootstrap = shift;
+	my $self = shift;
+	$bootstrap = shift;
 }
 
 
 sub child_init {
-    OpenSRF::System->bootstrap_client( config_file => $bootstrap );
-    
-    my $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
-    Fieldmapper->import(IDL => $idl);
-
-    $supercat = OpenSRF::AppSession->create('open-ils.supercat');
-    $actor = OpenSRF::AppSession->create('open-ils.actor');
-    $search = OpenSRF::AppSession->create('open-ils.search');
-    $parser = new XML::LibXML;
-    $xslt = new XML::LibXSLT;
-
-    $cn_browse_xslt = $parser->parse_file(
-            OpenSRF::Utils::SettingsClient
-                    ->new
-                    ->config_value( dirs => 'xsl' ).
-            "/CNBrowse2HTML.xsl"
-    );
+	OpenSRF::System->bootstrap_client( config_file => $bootstrap );
+	
+	my $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
+	Fieldmapper->import(IDL => $idl);
+
+	$supercat = OpenSRF::AppSession->create('open-ils.supercat');
+	$actor = OpenSRF::AppSession->create('open-ils.actor');
+	$search = OpenSRF::AppSession->create('open-ils.search');
+	$parser = new XML::LibXML;
+	$xslt = new XML::LibXSLT;
+
+        $cn_browse_xslt = $parser->parse_file(
+                OpenSRF::Utils::SettingsClient
+                        ->new
+                        ->config_value( dirs => 'xsl' ).
+                "/CNBrowse2HTML.xsl"
+        );
 
-    $cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
+	$cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
 
-    my $list = $supercat
-        ->request("open-ils.supercat.record.formats")
-        ->gather(1);
+	my $list = $supercat
+		->request("open-ils.supercat.record.formats")
+		->gather(1);
 
     $list = [ map { (keys %$_)[0] } @$list ];
     push @$list, 'htmlholdings','html', 'marctxt', 'ris';
@@ -157,22 +157,22 @@ sub child_init {
                 my $__a = $browse_axis;
 
                 $browse_types{$__a}{$__f} = sub {
-                    my $record_list = shift;
-                    my $prev = shift;
-                    my $next = shift;
-                    my $real_format = shift || $__f;
-                    my $unapi = shift;
-                    my $base = shift;
-                    my $site = shift;
-
-                    $log->info("Creating record feed with params [$real_format, $record_list, $unapi, $site]");
-                    my $feed = create_record_feed( 'record', $real_format, $record_list, $unapi, $site, undef, $real_format =~ /(-full|-uris)$/o ? 1 : 0 );
-                    $feed->root( "$base/../" );
-                    $feed->lib( $site );
-                    $feed->link( next => $next => $feed->type );
-                    $feed->link( previous => $prev => $feed->type );
-
-                    return (
+                	my $record_list = shift;
+                	my $prev = shift;
+                	my $next = shift;
+                	my $real_format = shift || $__f;
+                	my $unapi = shift;
+                	my $base = shift;
+                	my $site = shift;
+
+					$log->info("Creating record feed with params [$real_format, $record_list, $unapi, $site]");
+                	my $feed = create_record_feed( 'record', $real_format, $record_list, $unapi, $site, undef, $real_format =~ /(-full|-uris)$/o ? 1 : 0 );
+                	$feed->root( "$base/../" );
+                	$feed->lib( $site );
+                	$feed->link( next => $next => $feed->type );
+                	$feed->link( previous => $prev => $feed->type );
+
+                	return (
                         "Content-type: ". $feed->type ."; charset=utf-8\n\n",
                         $feed->toString
                     );
@@ -188,21 +188,21 @@ sub child_init {
                 my $__a = $browse_axis;
 
                 $browse_types{$__a}{$__f} = sub {
-                    my $record_list = shift;
-                    my $prev = shift;
-                    my $next = shift;
-                    my $real_format = shift || $__f;
-                    my $unapi = shift;
-                    my $base = shift;
-                    my $site = shift;
-
-                    $log->info("Creating record feed with params [$real_format, $record_list, $unapi, $site]");
-                    my $feed = create_record_feed( 'authority', $real_format, $record_list, $unapi, $site, undef, $real_format =~ /-full$/o ? -1 : 0 );
-                    $feed->root( "$base/../" );
-                    $feed->link( next => $next => $feed->type );
-                    $feed->link( previous => $prev => $feed->type );
-
-                    return (
+                	my $record_list = shift;
+                	my $prev = shift;
+                	my $next = shift;
+                	my $real_format = shift || $__f;
+                	my $unapi = shift;
+                	my $base = shift;
+                	my $site = shift;
+
+					$log->info("Creating record feed with params [$real_format, $record_list, $unapi, $site]");
+                	my $feed = create_record_feed( 'authority', $real_format, $record_list, $unapi, $site, undef, $real_format =~ /-full$/o ? -1 : 0 );
+                	$feed->root( "$base/../" );
+                	$feed->link( next => $next => $feed->type );
+                	$feed->link( previous => $prev => $feed->type );
+
+                	return (
                         "Content-type: ". $feed->type ."; charset=utf-8\n\n",
                         $feed->toString
                     );
@@ -228,18 +228,18 @@ Otherwise, we won't return any holdings.
 =cut
 
 sub parse_feed_type {
-    my $type = shift;
+	my $type = shift;
 
-     if ($type =~ /-full$/o) {
-        return 1;
-    }
+ 	if ($type =~ /-full$/o) {
+		return 1;
+	}
 
-     if ($type =~ /-uris$/o) {
-        return "uris";
-    }
+ 	if ($type =~ /-uris$/o) {
+		return "uris";
+	}
 
-    # Otherwise, we'll return just the facts, ma'am
-    return 0;
+	# Otherwise, we'll return just the facts, ma'am
+	return 0;
 }
 
 =head2 supercat_format($format_hashref, $format_type)
@@ -254,21 +254,21 @@ have to populate the hash with redundant information.
 =cut
 
 sub supercat_format {
-    my $h = shift;
-    my $type = shift;
+	my $h = shift;
+	my $type = shift;
 
-    (my $base_type = $type) =~ s/(-full|-uris)$//o;
+	(my $base_type = $type) =~ s/(-full|-uris)$//o;
 
-    my $format = "<format><name>$type</name><type>application/xml</type>";
+	my $format = "<format><name>$type</name><type>application/xml</type>";
 
-    for my $part ( qw/namespace_uri docs schema_location/ ) {
-        $format .= "<$part>$$h{$base_type}{$part}</$part>"
-            if ($$h{$base_type}{$part});
-    }
+	for my $part ( qw/namespace_uri docs schema_location/ ) {
+		$format .= "<$part>$$h{$base_type}{$part}</$part>"
+			if ($$h{$base_type}{$part});
+	}
 
-    $format .= '</format>';
+	$format .= '</format>';
 
-    return $format;
+	return $format;
 }
 
 =head2 unapi_format($format_hashref, $format_type)
@@ -283,199 +283,199 @@ have to populate the hash with redundant information.
 =cut
 
 sub unapi_format {
-    my $h = shift;
-    my $type = shift;
+	my $h = shift;
+	my $type = shift;
 
-    (my $base_type = $type) =~ s/(-full|-uris)$//o;
+	(my $base_type = $type) =~ s/(-full|-uris)$//o;
 
-    my $format = "<format name='$type' type='application/xml'";
+	my $format = "<format name='$type' type='application/xml'";
 
-    for my $part ( qw/namespace_uri docs schema_location/ ) {
-        $format .= " $part='$$h{$base_type}{$part}'"
-            if ($$h{$base_type}{$part});
-    }
+	for my $part ( qw/namespace_uri docs schema_location/ ) {
+		$format .= " $part='$$h{$base_type}{$part}'"
+			if ($$h{$base_type}{$part});
+	}
 
-    $format .= "/>\n";
+	$format .= "/>\n";
 
-    return $format;
+	return $format;
 }
 
 
 sub oisbn {
 
-    my $apache = shift;
-    return Apache2::Const::DECLINED if (-e $apache->filename);
+	my $apache = shift;
+	return Apache2::Const::DECLINED if (-e $apache->filename);
 
-    (my $isbn = $apache->path_info) =~ s{^.*?([^/]+)$}{$1}o;
+	(my $isbn = $apache->path_info) =~ s{^.*?([^/]+)$}{$1}o;
 
-    my $list = $supercat
-        ->request("open-ils.supercat.oisbn", $isbn)
-        ->gather(1);
+	my $list = $supercat
+		->request("open-ils.supercat.oisbn", $isbn)
+		->gather(1);
 
-    print "Content-type: application/xml; charset=utf-8\n\n";
-    print "<?xml version='1.0' encoding='UTF-8' ?>\n";
+	print "Content-type: application/xml; charset=utf-8\n\n";
+	print "<?xml version='1.0' encoding='UTF-8' ?>\n";
 
-    unless (exists $$list{metarecord}) {
-        print '<idlist/>';
-        return Apache2::Const::OK;
-    }
+	unless (exists $$list{metarecord}) {
+		print '<idlist/>';
+		return Apache2::Const::OK;
+	}
 
-    print "<idlist metarecord='$$list{metarecord}'>\n";
+	print "<idlist metarecord='$$list{metarecord}'>\n";
 
-    for ( keys %{ $$list{record_list} } ) {
-        (my $o = $$list{record_list}{$_}) =~s/^(\S+).*?$/$1/o;
-        print "  <isbn record='$_'>$o</isbn>\n"
-    }
+	for ( keys %{ $$list{record_list} } ) {
+		(my $o = $$list{record_list}{$_}) =~s/^(\S+).*?$/$1/o;
+		print "  <isbn record='$_'>$o</isbn>\n"
+	}
 
-    print "</idlist>\n";
+	print "</idlist>\n";
 
-    return Apache2::Const::OK;
+	return Apache2::Const::OK;
 }
 
 sub unapi {
 
-    my $apache = shift;
-    return Apache2::Const::DECLINED if (-e $apache->filename);
+	my $apache = shift;
+	return Apache2::Const::DECLINED if (-e $apache->filename);
 
-    my $cgi = new CGI;
+	my $cgi = new CGI;
 
-    my $add_path = 0;
-    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-        my $rel_name = $cgi->url(-relative=>1);
-        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-    }
+	my $add_path = 0;
+	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+		my $rel_name = $cgi->url(-relative=>1);
+		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+	}
 
-    my $url = $cgi->url(-path_info=>$add_path);
-    my $root = (split 'unapi', $url)[0];
-    my $base = (split 'unapi', $url)[0] . 'unapi';
+	my $url = $cgi->url(-path_info=>$add_path);
+	my $root = (split 'unapi', $url)[0];
+	my $base = (split 'unapi', $url)[0] . 'unapi';
 
 
-    my $uri = $cgi->param('id') || '';
-    my $host = $cgi->virtual_host || $cgi->server_name;
+	my $uri = $cgi->param('id') || '';
+	my $host = $cgi->virtual_host || $cgi->server_name;
 
-    my $skin = $cgi->param('skin') || 'default';
-    my $locale = $cgi->param('locale') || 'en-US';
-
-    # Enable localized results of copy status, etc
-    $supercat->session_locale($locale);
-
-    my $format = $cgi->param('format');
-    my $flesh_feed = parse_feed_type($format);
-    (my $base_format = $format) =~ s/(-full|-uris)$//o;
-    my ($id,$type,$command,$lib,$depth,$paging) = ('','','');
-
-    if (!$format) {
-        my $body = "Content-type: application/xml; charset=utf-8\n\n";
-    
-        if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^\/[]+)(?:\[([0-9,]+)\])?(?:/(.+))?}o) {
-            $id = $2;
-            $paging = $3;
-            ($lib,$depth) = split('/', $4);
-            $type = 'record';
-            $type = 'metarecord' if ($1 =~ /^m/o);
-            $type = 'authority' if ($1 =~ /^authority/o);
-
-            my $list = $supercat
-                ->request("open-ils.supercat.$type.formats")
-                ->gather(1);
-
-            if ($type eq 'record' or $type eq 'isbn') {
-                $body .= <<"                FORMATS";
-<formats id='$uri'>
-    <format name='opac' type='text/html'/>
-    <format name='html' type='text/html'/>
-    <format name='htmlholdings' type='text/html'/>
-    <format name='holdings_xml' type='application/xml'/>
-    <format name='holdings_xml-full' type='application/xml'/>
-    <format name='html-full' type='text/html'/>
-    <format name='htmlholdings-full' type='text/html'/>
-    <format name='marctxt' type='text/plain'/>
-    <format name='ris' type='text/plain'/>
-                FORMATS
-            } elsif ($type eq 'metarecord') {
-                $body .= <<"                FORMATS";
-                <formats id='$uri'>
-                    <format name='opac' type='text/html'/>
-                FORMATS
-            } else {
-                $body .= <<"                FORMATS";
-                <formats id='$uri'>
-                FORMATS
-            }
+	my $skin = $cgi->param('skin') || 'default';
+	my $locale = $cgi->param('locale') || 'en-US';
 
-            for my $h (@$list) {
-                my ($type) = keys %$h;
-                $body .= unapi_format($h, $type);
+	# Enable localized results of copy status, etc
+	$supercat->session_locale($locale);
 
-                if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
-                    $body .= unapi_format($h, "$type-full");
-                    $body .= unapi_format($h, "$type-uris");
-                }
-            }
+	my $format = $cgi->param('format');
+	my $flesh_feed = parse_feed_type($format);
+	(my $base_format = $format) =~ s/(-full|-uris)$//o;
+	my ($id,$type,$command,$lib,$depth,$paging) = ('','','');
 
-            $body .= "</formats>\n";
+	if (!$format) {
+		my $body = "Content-type: application/xml; charset=utf-8\n\n";
+	
+		if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^\/[]+)(?:\[([0-9,]+)\])?(?:/(.+))?}o) {
+			$id = $2;
+			$paging = $3;
+			($lib,$depth) = split('/', $4);
+			$type = 'record';
+			$type = 'metarecord' if ($1 =~ /^m/o);
+			$type = 'authority' if ($1 =~ /^authority/o);
 
-        } else {
-            my $list = $supercat
-                ->request("open-ils.supercat.$type.formats")
-                ->gather(1);
-                
-            push @$list,
-                @{ $supercat
-                    ->request("open-ils.supercat.metarecord.formats")
-                    ->gather(1);
-                };
+			my $list = $supercat
+				->request("open-ils.supercat.$type.formats")
+				->gather(1);
 
-            my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
-            $list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
-
-            $body .= <<"            FORMATS";
+			if ($type eq 'record' or $type eq 'isbn') {
+				$body .= <<"				FORMATS";
+<formats id='$uri'>
+	<format name='opac' type='text/html'/>
+	<format name='html' type='text/html'/>
+	<format name='htmlholdings' type='text/html'/>
+	<format name='holdings_xml' type='application/xml'/>
+	<format name='holdings_xml-full' type='application/xml'/>
+	<format name='html-full' type='text/html'/>
+	<format name='htmlholdings-full' type='text/html'/>
+	<format name='marctxt' type='text/plain'/>
+	<format name='ris' type='text/plain'/>
+				FORMATS
+			} elsif ($type eq 'metarecord') {
+				$body .= <<"				FORMATS";
+				<formats id='$uri'>
+					<format name='opac' type='text/html'/>
+				FORMATS
+			} else {
+				$body .= <<"				FORMATS";
+				<formats id='$uri'>
+				FORMATS
+			}
+
+			for my $h (@$list) {
+				my ($type) = keys %$h;
+				$body .= unapi_format($h, $type);
+
+				if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
+					$body .= unapi_format($h, "$type-full");
+					$body .= unapi_format($h, "$type-uris");
+				}
+			}
+
+			$body .= "</formats>\n";
+
+		} else {
+			my $list = $supercat
+				->request("open-ils.supercat.$type.formats")
+				->gather(1);
+				
+			push @$list,
+				@{ $supercat
+					->request("open-ils.supercat.metarecord.formats")
+					->gather(1);
+				};
+
+			my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
+			$list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
+
+			$body .= <<"			FORMATS";
 <formats>
-    <format name='opac' type='text/html'/>
-    <format name='html' type='text/html'/>
-    <format name='htmlholdings' type='text/html'/>
-    <format name='holdings_xml' type='application/xml'/>
-    <format name='holdings_xml-full' type='application/xml'/>
-    <format name='html-full' type='text/html'/>
-    <format name='htmlholdings-full' type='text/html'/>
-    <format name='marctxt' type='text/plain'/>
-    <format name='ris' type='text/plain'/>
-            FORMATS
-
-
-            for my $h (@$list) {
-                my ($type) = keys %$h;
-                $body .= "\t" . unapi_format($h, $type);
-
-                if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
-                    $body .= "\t" . unapi_format($h, "$type-full");
-                    $body .= "\t" . unapi_format($h, "$type-uris");
-                }
-            }
-
-            $body .= "</formats>\n";
-
-        }
-        print $body;
-        return Apache2::Const::OK;
-    }
-
-    my $scheme;
-    if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^\/[]+)(?:\[([0-9,]+)\])?(?:/(.+))?}o) {
-        $scheme = $1;
-        $id = $2;
-        $paging = $3;
-        ($lib,$depth) = split('/', $4);
-        $type = 'record';
-        $type = 'metarecord' if ($scheme =~ /^metabib/o);
-        $type = 'isbn' if ($scheme =~ /^isbn/o);
-        $type = 'acp' if ($scheme =~ /^asset-copy/o);
-        $type = 'acn' if ($scheme =~ /^asset-call_number/o);
-        $type = 'auri' if ($scheme =~ /^asset-uri/o);
-        $type = 'authority' if ($scheme =~ /^authority/o);
-        $command = 'retrieve';
-        $command = 'browse' if (grep { $scheme eq $_ } qw/call_number title author subject topic authority.title authority.author authority.subject authority.topic series item-age/);
-    }
+	<format name='opac' type='text/html'/>
+	<format name='html' type='text/html'/>
+	<format name='htmlholdings' type='text/html'/>
+	<format name='holdings_xml' type='application/xml'/>
+	<format name='holdings_xml-full' type='application/xml'/>
+	<format name='html-full' type='text/html'/>
+	<format name='htmlholdings-full' type='text/html'/>
+	<format name='marctxt' type='text/plain'/>
+	<format name='ris' type='text/plain'/>
+			FORMATS
+
+
+			for my $h (@$list) {
+				my ($type) = keys %$h;
+				$body .= "\t" . unapi_format($h, $type);
+
+				if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
+					$body .= "\t" . unapi_format($h, "$type-full");
+					$body .= "\t" . unapi_format($h, "$type-uris");
+				}
+			}
+
+			$body .= "</formats>\n";
+
+		}
+		print $body;
+		return Apache2::Const::OK;
+	}
+
+	my $scheme;
+	if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^\/[]+)(?:\[([0-9,]+)\])?(?:/(.+))?}o) {
+		$scheme = $1;
+		$id = $2;
+		$paging = $3;
+		($lib,$depth) = split('/', $4);
+		$type = 'record';
+		$type = 'metarecord' if ($scheme =~ /^metabib/o);
+		$type = 'isbn' if ($scheme =~ /^isbn/o);
+		$type = 'acp' if ($scheme =~ /^asset-copy/o);
+		$type = 'acn' if ($scheme =~ /^asset-call_number/o);
+		$type = 'auri' if ($scheme =~ /^asset-uri/o);
+		$type = 'authority' if ($scheme =~ /^authority/o);
+		$command = 'retrieve';
+		$command = 'browse' if (grep { $scheme eq $_ } qw/call_number title author subject topic authority.title authority.author authority.subject authority.topic series item-age/);
+	}
 
     if ($paging) {
         $paging = [split ',', $paging];
@@ -483,563 +483,563 @@ sub unapi {
         $paging = [];
     }
 
-    if (!$lib || $lib eq '-') {
-         $lib = $actor->request(
-            'open-ils.actor.org_unit_list.search' => parent_ou => undef
-        )->gather(1)->[0]->shortname;
-    }
-
-    my ($lib_object,$lib_id,$ou_types,$lib_depth);
-    if ($type ne 'acn' && $type ne 'acp' && $type ne 'auri') {
-        $lib_object = $actor->request(
-            'open-ils.actor.org_unit_list.search' => shortname => $lib
-        )->gather(1)->[0];
-        $lib_id = $lib_object->id;
-
-        $ou_types = $actor->request( 'open-ils.actor.org_types.retrieve' )->gather(1);
-        $lib_depth = $depth || (grep { $_->id == $lib_object->ou_type } @$ou_types)[0]->depth;
-    }
-
-    if ($command eq 'browse') {
-        print "Location: $root/browse/$base_format/$scheme/$lib/$id\n\n";
-        return 302;
-    }
-
-    if ($type eq 'isbn') {
-        my $rec = $supercat->request('open-ils.supercat.isbn.object.retrieve',$id)->gather(1);
-        if (!@$rec) {
-            print "Content-type: text/html; charset=utf-8\n\n";
-            $apache->custom_response( 404, <<"            HTML");
-            <html>
-                <head>
-                    <title>Type [$type] with id [$id] not found!</title>
-                </head>
-                <body>
-                    <br/>
-                    <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
-                </body>
-            </html>
-            HTML
-            return 404;
-        }
-        $id = $rec->[0]->id;
-        $type = 'record';
-    }
-
-    if ( !grep
-           { (keys(%$_))[0] eq $base_format }
-           @{ $supercat->request("open-ils.supercat.$type.formats")->gather(1) }
-         and !grep
-           { $_ eq $base_format }
-           qw/opac html htmlholdings marctxt ris holdings_xml/
-    ) {
-        print "Content-type: text/html; charset=utf-8\n\n";
-        $apache->custom_response( 406, <<"        HTML");
-        <html>
-            <head>
-                <title>Invalid format [$format] for type [$type]!</title>
-            </head>
-            <body>
-                <br/>
-                <center>Sorry, format $format is not valid for type $type.</center>
-            </body>
-        </html>
-        HTML
-        return 406;
-    }
-
-    if ($format eq 'opac') {
-        print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id&l=$lib_id&d=$lib_depth\n\n"
-            if ($type eq 'metarecord');
-        print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id&l=$lib_id&d=$lib_depth\n\n"
-            if ($type eq 'record');
-        return 302;
-    } elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format) && ($type ne 'acn' && $type ne 'acp' && $type ne 'auri')) {
-        my $feed = create_record_feed(
-            $type,
-            $format => [ $id ],
-            $base,
-            $lib,
-            $depth,
-            $flesh_feed,
+	if (!$lib || $lib eq '-') {
+	 	$lib = $actor->request(
+			'open-ils.actor.org_unit_list.search' => parent_ou => undef
+		)->gather(1)->[0]->shortname;
+	}
+
+	my ($lib_object,$lib_id,$ou_types,$lib_depth);
+	if ($type ne 'acn' && $type ne 'acp' && $type ne 'auri') {
+		$lib_object = $actor->request(
+			'open-ils.actor.org_unit_list.search' => shortname => $lib
+		)->gather(1)->[0];
+		$lib_id = $lib_object->id;
+
+		$ou_types = $actor->request( 'open-ils.actor.org_types.retrieve' )->gather(1);
+		$lib_depth = $depth || (grep { $_->id == $lib_object->ou_type } @$ou_types)[0]->depth;
+	}
+
+	if ($command eq 'browse') {
+		print "Location: $root/browse/$base_format/$scheme/$lib/$id\n\n";
+		return 302;
+	}
+
+	if ($type eq 'isbn') {
+		my $rec = $supercat->request('open-ils.supercat.isbn.object.retrieve',$id)->gather(1);
+		if (!@$rec) {
+			print "Content-type: text/html; charset=utf-8\n\n";
+			$apache->custom_response( 404, <<"			HTML");
+			<html>
+				<head>
+					<title>Type [$type] with id [$id] not found!</title>
+				</head>
+				<body>
+					<br/>
+					<center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
+				</body>
+			</html>
+			HTML
+			return 404;
+		}
+		$id = $rec->[0]->id;
+		$type = 'record';
+	}
+
+	if ( !grep
+		   { (keys(%$_))[0] eq $base_format }
+		   @{ $supercat->request("open-ils.supercat.$type.formats")->gather(1) }
+		 and !grep
+		   { $_ eq $base_format }
+		   qw/opac html htmlholdings marctxt ris holdings_xml/
+	) {
+		print "Content-type: text/html; charset=utf-8\n\n";
+		$apache->custom_response( 406, <<"		HTML");
+		<html>
+			<head>
+				<title>Invalid format [$format] for type [$type]!</title>
+			</head>
+			<body>
+				<br/>
+				<center>Sorry, format $format is not valid for type $type.</center>
+			</body>
+		</html>
+		HTML
+		return 406;
+	}
+
+	if ($format eq 'opac') {
+		print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id&l=$lib_id&d=$lib_depth\n\n"
+			if ($type eq 'metarecord');
+		print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id&l=$lib_id&d=$lib_depth\n\n"
+			if ($type eq 'record');
+		return 302;
+	} elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format) && ($type ne 'acn' && $type ne 'acp' && $type ne 'auri')) {
+		my $feed = create_record_feed(
+			$type,
+			$format => [ $id ],
+			$base,
+			$lib,
+			$depth,
+			$flesh_feed,
             $paging
-        );
-
-        if (!$feed->count) {
-            print "Content-type: text/html; charset=utf-8\n\n";
-            $apache->custom_response( 404, <<"            HTML");
-            <html>
-                <head>
-                    <title>Type [$type] with id [$id] not found!</title>
-                </head>
-                <body>
-                    <br/>
-                    <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
-                </body>
-            </html>
-            HTML
-            return 404;
-        }
-
-        $feed->root($root);
-        $feed->creator($host);
-        $feed->update_ts();
-        $feed->link( unapi => $base) if ($flesh_feed);
-
-        print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
-        print $U->entityize($feed->toString) . "\n";
-
-        return Apache2::Const::OK;
-    }
-
-    my $method = "open-ils.supercat.$type.$base_format.$command";
-    my @params = ($id);
-    push @params, $lib, $lib_depth, $flesh_feed, $paging if ($base_format eq 'holdings_xml');
-
-    # for acn, acp, etc, the "lib" pathinfo position isn't useful.
-    # however, we can have it carry extra options like no_record! (comma separated)
-    push @params, { map { ( $_ => 1 ) } split(',', $lib) } if ( grep { $type eq $_} qw/acn acp auri/);
-
-    my $req = $supercat->request($method, at params);
-    my $data = $req->gather();
-
-    if ($req->failed || !$data) {
-        print "Content-type: text/html; charset=utf-8\n\n";
-        $apache->custom_response( 404, <<"        HTML");
-        <html>
-            <head>
-                <title>$type $id not found!</title>
-            </head>
-            <body>
-                <br/>
-                <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
-            </body>
-        </html>
-        HTML
-        return 404;
-    }
-
-    print "Content-type: application/xml; charset=utf-8\n\n$data";
-
-    if ($base_format eq 'holdings_xml') {
-        while (my $c = $req->recv) {
-            print $c->content;
-        }
-    }
-
-    return Apache2::Const::OK;
+		);
+
+		if (!$feed->count) {
+			print "Content-type: text/html; charset=utf-8\n\n";
+			$apache->custom_response( 404, <<"			HTML");
+			<html>
+				<head>
+					<title>Type [$type] with id [$id] not found!</title>
+				</head>
+				<body>
+					<br/>
+					<center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
+				</body>
+			</html>
+			HTML
+			return 404;
+		}
+
+		$feed->root($root);
+		$feed->creator($host);
+		$feed->update_ts();
+		$feed->link( unapi => $base) if ($flesh_feed);
+
+		print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
+		print $U->entityize($feed->toString) . "\n";
+
+		return Apache2::Const::OK;
+	}
+
+	my $method = "open-ils.supercat.$type.$base_format.$command";
+	my @params = ($id);
+	push @params, $lib, $lib_depth, $flesh_feed, $paging if ($base_format eq 'holdings_xml');
+
+	# for acn, acp, etc, the "lib" pathinfo position isn't useful.
+	# however, we can have it carry extra options like no_record! (comma separated)
+	push @params, { map { ( $_ => 1 ) } split(',', $lib) } if ( grep { $type eq $_} qw/acn acp auri/);
+
+	my $req = $supercat->request($method, at params);
+	my $data = $req->gather();
+
+	if ($req->failed || !$data) {
+		print "Content-type: text/html; charset=utf-8\n\n";
+		$apache->custom_response( 404, <<"		HTML");
+		<html>
+			<head>
+				<title>$type $id not found!</title>
+			</head>
+			<body>
+				<br/>
+				<center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
+			</body>
+		</html>
+		HTML
+		return 404;
+	}
+
+	print "Content-type: application/xml; charset=utf-8\n\n$data";
+
+	if ($base_format eq 'holdings_xml') {
+		while (my $c = $req->recv) {
+			print $c->content;
+		}
+	}
+
+	return Apache2::Const::OK;
 }
 
 sub supercat {
 
-    my $apache = shift;
-    return Apache2::Const::DECLINED if (-e $apache->filename);
-
-    my $cgi = new CGI;
-
-    my $add_path = 0;
-    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-        my $rel_name = $cgi->url(-relative=>1);
-        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-    }
-
-    my $url = $cgi->url(-path_info=>$add_path);
-    my $root = (split 'supercat', $url)[0];
-    my $base = (split 'supercat', $url)[0] . 'supercat';
-    my $unapi = (split 'supercat', $url)[0] . 'unapi';
-
-    my $host = $cgi->virtual_host || $cgi->server_name;
-
-    my $path = $cgi->path_info;
-    my ($id,$type,$format,$command) = reverse split '/', $path;
-    my $flesh_feed = parse_feed_type($format);
-    (my $base_format = $format) =~ s/(-full|-uris)$//o;
-
-    my $skin = $cgi->param('skin') || 'default';
-    my $locale = $cgi->param('locale') || 'en-US';
-
-    # Enable localized results of copy status, etc
-    $supercat->session_locale($locale);
-    
-    if ( $path =~ m{^/formats(?:/([^\/]+))?$}o ) {
-        print "Content-type: application/xml; charset=utf-8\n";
-        if ($1) {
-            my $list = $supercat
-                ->request("open-ils.supercat.$1.formats")
-                ->gather(1);
-
-            print "\n";
-
-            print "<formats>
-                   <format>
-                     <name>opac</name>
-                     <type>text/html</type>
-                   </format>";
-
-            if ($1 eq 'record' or $1 eq 'isbn') {
-                print "<format>
-                     <name>htmlholdings</name>
-                     <type>text/html</type>
-                   </format>
-                   <format>
-                     <name>html</name>
-                     <type>text/html</type>
-                   </format>
-                   <format>
-                     <name>htmlholdings-full</name>
-                     <type>text/html</type>
-                   </format>
-                   <format>
-                     <name>html-full</name>
-                     <type>text/html</type>
-                   </format>
-                   <format>
-                     <name>marctxt</name>
-                     <type>text/plain</type>
-                   </format>
-                   <format>
-                     <name>ris</name>
-                     <type>text/plain</type>
-                   </format>";
-            }
-
-            for my $h (@$list) {
-                my ($type) = keys %$h;
-                print supercat_format($h, $type);
-
-                if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
-                    print supercat_format($h, "$type-full");
-                    print supercat_format($h, "$type-uris");
-                }
-
-            }
-
-            print "</formats>\n";
-
-            return Apache2::Const::OK;
-        }
-
-        my $list = $supercat
-            ->request("open-ils.supercat.record.formats")
-            ->gather(1);
-                
-        push @$list,
-            @{ $supercat
-                ->request("open-ils.supercat.metarecord.formats")
-                ->gather(1);
-            };
-
-        my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
-        $list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
-
-        print "\n<formats>
-               <format>
-                 <name>opac</name>
-                 <type>text/html</type>
-               </format>
-               <format>
-                 <name>htmlholdings</name>
-                 <type>text/html</type>
-               </format>
-               <format>
-                 <name>html</name>
-                 <type>text/html</type>
-               </format>
-               <format>
-                 <name>htmlholdings-full</name>
-                 <type>text/html</type>
-               </format>
-               <format>
-                 <name>html-full</name>
-                 <type>text/html</type>
-               </format>
-               <format>
-                 <name>marctxt</name>
-                 <type>text/plain</type>
-               </format>
-               <format>
-                 <name>ris</name>
-                 <type>text/plain</type>
-               </format>";
-
-        for my $h (@$list) {
-            my ($type) = keys %$h;
-            print supercat_format($h, $type);
-
-            if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
-                print supercat_format($h, "$type-full");
-                print supercat_format($h, "$type-uris");
-            }
-
-        }
-
-        print "</formats>\n";
-
-
-        return Apache2::Const::OK;
-    }
-
-    if ($format eq 'opac') {
-        print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id\n\n"
-            if ($type eq 'metarecord');
-        print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id\n\n"
-            if ($type eq 'record');
-        return 302;
-
-    } elsif ($base_format eq 'marc21') {
-
-        my $ret = 200;    
-        try {
-            my $bib = $supercat->request( "open-ils.supercat.record.object.retrieve", $id )->gather(1)->[0];
+	my $apache = shift;
+	return Apache2::Const::DECLINED if (-e $apache->filename);
+
+	my $cgi = new CGI;
+
+	my $add_path = 0;
+	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+		my $rel_name = $cgi->url(-relative=>1);
+		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+	}
+
+	my $url = $cgi->url(-path_info=>$add_path);
+	my $root = (split 'supercat', $url)[0];
+	my $base = (split 'supercat', $url)[0] . 'supercat';
+	my $unapi = (split 'supercat', $url)[0] . 'unapi';
+
+	my $host = $cgi->virtual_host || $cgi->server_name;
+
+	my $path = $cgi->path_info;
+	my ($id,$type,$format,$command) = reverse split '/', $path;
+	my $flesh_feed = parse_feed_type($format);
+	(my $base_format = $format) =~ s/(-full|-uris)$//o;
+
+	my $skin = $cgi->param('skin') || 'default';
+	my $locale = $cgi->param('locale') || 'en-US';
+
+	# Enable localized results of copy status, etc
+	$supercat->session_locale($locale);
+	
+	if ( $path =~ m{^/formats(?:/([^\/]+))?$}o ) {
+		print "Content-type: application/xml; charset=utf-8\n";
+		if ($1) {
+			my $list = $supercat
+				->request("open-ils.supercat.$1.formats")
+				->gather(1);
+
+			print "\n";
+
+			print "<formats>
+				   <format>
+				     <name>opac</name>
+				     <type>text/html</type>
+				   </format>";
+
+			if ($1 eq 'record' or $1 eq 'isbn') {
+				print "<format>
+				     <name>htmlholdings</name>
+				     <type>text/html</type>
+				   </format>
+				   <format>
+				     <name>html</name>
+				     <type>text/html</type>
+				   </format>
+				   <format>
+				     <name>htmlholdings-full</name>
+				     <type>text/html</type>
+				   </format>
+				   <format>
+				     <name>html-full</name>
+				     <type>text/html</type>
+				   </format>
+				   <format>
+				     <name>marctxt</name>
+				     <type>text/plain</type>
+				   </format>
+				   <format>
+				     <name>ris</name>
+				     <type>text/plain</type>
+				   </format>";
+			}
+
+			for my $h (@$list) {
+				my ($type) = keys %$h;
+				print supercat_format($h, $type);
+
+				if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
+					print supercat_format($h, "$type-full");
+					print supercat_format($h, "$type-uris");
+				}
+
+			}
+
+			print "</formats>\n";
+
+			return Apache2::Const::OK;
+		}
+
+		my $list = $supercat
+			->request("open-ils.supercat.record.formats")
+			->gather(1);
+				
+		push @$list,
+			@{ $supercat
+				->request("open-ils.supercat.metarecord.formats")
+				->gather(1);
+			};
+
+		my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
+		$list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
+
+		print "\n<formats>
+			   <format>
+			     <name>opac</name>
+			     <type>text/html</type>
+			   </format>
+			   <format>
+			     <name>htmlholdings</name>
+			     <type>text/html</type>
+			   </format>
+			   <format>
+			     <name>html</name>
+			     <type>text/html</type>
+			   </format>
+			   <format>
+			     <name>htmlholdings-full</name>
+			     <type>text/html</type>
+			   </format>
+			   <format>
+			     <name>html-full</name>
+			     <type>text/html</type>
+			   </format>
+			   <format>
+			     <name>marctxt</name>
+			     <type>text/plain</type>
+			   </format>
+			   <format>
+			     <name>ris</name>
+			     <type>text/plain</type>
+			   </format>";
+
+		for my $h (@$list) {
+			my ($type) = keys %$h;
+			print supercat_format($h, $type);
+
+			if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
+				print supercat_format($h, "$type-full");
+				print supercat_format($h, "$type-uris");
+			}
+
+		}
+
+		print "</formats>\n";
+
+
+		return Apache2::Const::OK;
+	}
+
+	if ($format eq 'opac') {
+		print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id\n\n"
+			if ($type eq 'metarecord');
+		print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id\n\n"
+			if ($type eq 'record');
+		return 302;
+
+	} elsif ($base_format eq 'marc21') {
+
+		my $ret = 200;    
+		try {
+			my $bib = $supercat->request( "open-ils.supercat.record.object.retrieve", $id )->gather(1)->[0];
         
-            print "Content-type: application/octet-stream\n\n" . MARC::Record->new_from_xml( $bib->marc, 'UTF-8', 'USMARC' )->as_usmarc;
-
-        } otherwise {
-            warn shift();
-            
-            print "Content-type: text/html; charset=utf-8\n\n";
-            $apache->custom_response( 404, <<"            HTML");
-            <html>
-                <head>
-                    <title>ERROR</title>
-                </head>
-                <body>
-                    <br/>
-                    <center>Couldn't fetch $id as MARC21.</center>
-                </body>
-            </html>
-            HTML
-            $ret = 404;
-        };
-
-        return Apache2::Const::OK;
-
-    } elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format)) {
-        my $feed = create_record_feed(
-            $type,
-            $format => [ $id ],
-            undef, undef, undef,
-            $flesh_feed
-        );
-
-        $feed->root($root);
-        $feed->creator($host);
-
-        $feed->update_ts();
-
-        $feed->link( unapi => $base) if ($flesh_feed);
-
-        print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
-        print $U->entityize($feed->toString) . "\n";
-
-        return Apache2::Const::OK;
-    }
-
-    my $req = $supercat->request("open-ils.supercat.$type.$format.$command",$id);
-    $req->wait_complete;
-
-    if ($req->failed) {
-        print "Content-type: text/html; charset=utf-8\n\n";
-        $apache->custom_response( 404, <<"        HTML");
-        <html>
-            <head>
-                <title>$type $id not found!</title>
-            </head>
-            <body>
-                <br/>
-                <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
-            </body>
-        </html>
-        HTML
-        return 404;
-    }
-
-    print "Content-type: application/xml; charset=utf-8\n\n";
-    print $U->entityize( $parser->parse_string( $req->gather(1) )->documentElement->toString );
-
-    return Apache2::Const::OK;
+			print "Content-type: application/octet-stream\n\n" . MARC::Record->new_from_xml( $bib->marc, 'UTF-8', 'USMARC' )->as_usmarc;
+
+		} otherwise {
+			warn shift();
+			
+			print "Content-type: text/html; charset=utf-8\n\n";
+			$apache->custom_response( 404, <<"			HTML");
+			<html>
+				<head>
+					<title>ERROR</title>
+				</head>
+				<body>
+					<br/>
+					<center>Couldn't fetch $id as MARC21.</center>
+				</body>
+			</html>
+			HTML
+			$ret = 404;
+		};
+
+		return Apache2::Const::OK;
+
+	} elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format)) {
+		my $feed = create_record_feed(
+			$type,
+			$format => [ $id ],
+			undef, undef, undef,
+			$flesh_feed
+		);
+
+		$feed->root($root);
+		$feed->creator($host);
+
+		$feed->update_ts();
+
+		$feed->link( unapi => $base) if ($flesh_feed);
+
+		print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
+		print $U->entityize($feed->toString) . "\n";
+
+		return Apache2::Const::OK;
+	}
+
+	my $req = $supercat->request("open-ils.supercat.$type.$format.$command",$id);
+	$req->wait_complete;
+
+	if ($req->failed) {
+		print "Content-type: text/html; charset=utf-8\n\n";
+		$apache->custom_response( 404, <<"		HTML");
+		<html>
+			<head>
+				<title>$type $id not found!</title>
+			</head>
+			<body>
+				<br/>
+				<center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
+			</body>
+		</html>
+		HTML
+		return 404;
+	}
+
+	print "Content-type: application/xml; charset=utf-8\n\n";
+	print $U->entityize( $parser->parse_string( $req->gather(1) )->documentElement->toString );
+
+	return Apache2::Const::OK;
 }
 
 
 sub bookbag_feed {
-    my $apache = shift;
-    return Apache2::Const::DECLINED if (-e $apache->filename);
-
-    my $cgi = new CGI;
-
-    my $year = (gmtime())[5] + 1900;
-    my $host = $cgi->virtual_host || $cgi->server_name;
-
-    my $add_path = 0;
-    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-        my $rel_name = $cgi->url(-relative=>1);
-        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-    }
-
-    my $url = $cgi->url(-path_info=>$add_path);
-    my $root = (split 'feed', $url)[0] . '/';
-    my $base = (split 'bookbag', $url)[0] . '/bookbag';
-    my $unapi = (split 'feed', $url)[0] . '/unapi';
-
-    my $skin = $cgi->param('skin') || 'default';
-    my $locale = $cgi->param('locale') || 'en-US';
-    my $org = $cgi->param('searchOrg');
-
-    # Enable localized results of copy status, etc
-    $supercat->session_locale($locale);
-
-    my $org_unit = get_ou($org);
-    my $scope = "l=" . $org_unit->[0]->id . "&";
-
-    $root =~ s{(?<!http:)//}{/}go;
-    $base =~ s{(?<!http:)//}{/}go;
-    $unapi =~ s{(?<!http:)//}{/}go;
-
-    my $path = $cgi->path_info;
-    #warn "URL breakdown: $url -> $root -> $base -> $path -> $unapi";
-
-    my ($id,$type) = reverse split '/', $path;
-    my $flesh_feed = parse_feed_type($type);
-
-    my $bucket = $actor->request("open-ils.actor.container.public.flesh", 'biblio', $id)->gather(1);
-    return Apache2::Const::NOT_FOUND unless($bucket);
-
-    my $bucket_tag = "tag:$host,$year:record_bucket/$id";
-    if ($type eq 'opac') {
-        print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
-            join('&', map { "rl=" . $_->target_biblio_record_entry } @{ $bucket->items }) .
-            "\n\n";
-        return 302;
-    }
-
-    my $feed = create_record_feed(
-        'record',
-        $type,
-        [ map { $_->target_biblio_record_entry } @{ $bucket->items } ],
-        $unapi,
-        $org_unit->[0]->shortname,
-        undef,
-        $flesh_feed
-    );
-    $feed->root($root);
-    $feed->id($bucket_tag);
-
-    $feed->title("Items in Book Bag [".$bucket->name."]");
-    $feed->creator($host);
-    $feed->update_ts();
-
-    $feed->link(alternate => $base . "/rss2-full/$id" => 'application/rss+xml');
-    $feed->link(atom => $base . "/atom-full/$id" => 'application/atom+xml');
-    $feed->link(html => $base . "/html-full/$id" => 'text/html');
-    $feed->link(unapi => $unapi);
-
-    $feed->link(
-        OPAC =>
-        "http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
-            join('&', map { 'rl=' . $_->target_biblio_record_entry } @{$bucket->items} ),
-        'text/html'
-    );
-
-
-    print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
-    print $U->entityize($feed->toString) . "\n";
-
-    return Apache2::Const::OK;
+	my $apache = shift;
+	return Apache2::Const::DECLINED if (-e $apache->filename);
+
+	my $cgi = new CGI;
+
+	my $year = (gmtime())[5] + 1900;
+	my $host = $cgi->virtual_host || $cgi->server_name;
+
+	my $add_path = 0;
+	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+		my $rel_name = $cgi->url(-relative=>1);
+		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+	}
+
+	my $url = $cgi->url(-path_info=>$add_path);
+	my $root = (split 'feed', $url)[0] . '/';
+	my $base = (split 'bookbag', $url)[0] . '/bookbag';
+	my $unapi = (split 'feed', $url)[0] . '/unapi';
+
+	my $skin = $cgi->param('skin') || 'default';
+	my $locale = $cgi->param('locale') || 'en-US';
+	my $org = $cgi->param('searchOrg');
+
+	# Enable localized results of copy status, etc
+	$supercat->session_locale($locale);
+
+	my $org_unit = get_ou($org);
+	my $scope = "l=" . $org_unit->[0]->id . "&";
+
+	$root =~ s{(?<!http:)//}{/}go;
+	$base =~ s{(?<!http:)//}{/}go;
+	$unapi =~ s{(?<!http:)//}{/}go;
+
+	my $path = $cgi->path_info;
+	#warn "URL breakdown: $url -> $root -> $base -> $path -> $unapi";
+
+	my ($id,$type) = reverse split '/', $path;
+	my $flesh_feed = parse_feed_type($type);
+
+	my $bucket = $actor->request("open-ils.actor.container.public.flesh", 'biblio', $id)->gather(1);
+	return Apache2::Const::NOT_FOUND unless($bucket);
+
+	my $bucket_tag = "tag:$host,$year:record_bucket/$id";
+	if ($type eq 'opac') {
+		print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
+			join('&', map { "rl=" . $_->target_biblio_record_entry } @{ $bucket->items }) .
+			"\n\n";
+		return 302;
+	}
+
+	my $feed = create_record_feed(
+		'record',
+		$type,
+		[ map { $_->target_biblio_record_entry } @{ $bucket->items } ],
+		$unapi,
+		$org_unit->[0]->shortname,
+		undef,
+		$flesh_feed
+	);
+	$feed->root($root);
+	$feed->id($bucket_tag);
+
+	$feed->title("Items in Book Bag [".$bucket->name."]");
+	$feed->creator($host);
+	$feed->update_ts();
+
+	$feed->link(alternate => $base . "/rss2-full/$id" => 'application/rss+xml');
+	$feed->link(atom => $base . "/atom-full/$id" => 'application/atom+xml');
+	$feed->link(html => $base . "/html-full/$id" => 'text/html');
+	$feed->link(unapi => $unapi);
+
+	$feed->link(
+		OPAC =>
+		"http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
+			join('&', map { 'rl=' . $_->target_biblio_record_entry } @{$bucket->items} ),
+		'text/html'
+	);
+
+
+	print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
+	print $U->entityize($feed->toString) . "\n";
+
+	return Apache2::Const::OK;
 }
 
 sub changes_feed {
-    my $apache = shift;
-    return Apache2::Const::DECLINED if (-e $apache->filename);
+	my $apache = shift;
+	return Apache2::Const::DECLINED if (-e $apache->filename);
 
-    my $cgi = new CGI;
+	my $cgi = new CGI;
 
-    my $year = (gmtime())[5] + 1900;
-    my $host = $cgi->virtual_host || $cgi->server_name;
+	my $year = (gmtime())[5] + 1900;
+	my $host = $cgi->virtual_host || $cgi->server_name;
 
-    my $add_path = 0;
-    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-        my $rel_name = $cgi->url(-relative=>1);
-        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-    }
+	my $add_path = 0;
+	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+		my $rel_name = $cgi->url(-relative=>1);
+		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+	}
 
-    my $url = $cgi->url(-path_info=>$add_path);
-    my $root = (split 'feed', $url)[0];
-    my $base = (split 'freshmeat', $url)[0] . '/freshmeat';
-    my $unapi = (split 'feed', $url)[0] . 'unapi';
+	my $url = $cgi->url(-path_info=>$add_path);
+	my $root = (split 'feed', $url)[0];
+	my $base = (split 'freshmeat', $url)[0] . '/freshmeat';
+	my $unapi = (split 'feed', $url)[0] . 'unapi';
 
-    my $skin = $cgi->param('skin') || 'default';
-    my $locale = $cgi->param('locale') || 'en-US';
-    my $org = $cgi->param('searchOrg');
+	my $skin = $cgi->param('skin') || 'default';
+	my $locale = $cgi->param('locale') || 'en-US';
+	my $org = $cgi->param('searchOrg');
 
-    # Enable localized results of copy status, etc
-    $supercat->session_locale($locale);
+	# Enable localized results of copy status, etc
+	$supercat->session_locale($locale);
 
-    my $org_unit = get_ou($org);
-    my $scope = "l=" . $org_unit->[0]->id . "&";
+	my $org_unit = get_ou($org);
+	my $scope = "l=" . $org_unit->[0]->id . "&";
 
-    my $path = $cgi->path_info;
-    #warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
+	my $path = $cgi->path_info;
+	#warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
 
-    $path =~ s/^\/(?:feed\/)?freshmeat\///og;
-    
-    my ($type,$rtype,$axis,$limit,$date) = split '/', $path;
-    my $flesh_feed = parse_feed_type($type);
+	$path =~ s/^\/(?:feed\/)?freshmeat\///og;
+	
+	my ($type,$rtype,$axis,$limit,$date) = split '/', $path;
+	my $flesh_feed = parse_feed_type($type);
 
-    $limit ||= 10;
-    $limit = 10 if $limit !~ /^\d+$/;
+	$limit ||= 10;
+	$limit = 10 if $limit !~ /^\d+$/;
 
-    my $list = $supercat->request("open-ils.supercat.$rtype.record.$axis.recent", $date, $limit)->gather(1);
+	my $list = $supercat->request("open-ils.supercat.$rtype.record.$axis.recent", $date, $limit)->gather(1);
 
-    #if ($type eq 'opac') {
-    #    print "Location: $root/../../en-US/skin/default/xml/rresult.xml?rt=list&" .
-    #        join('&', map { "rl=" . $_ } @$list) .
-    #        "\n\n";
-    #    return 302;
-    #}
+	#if ($type eq 'opac') {
+	#	print "Location: $root/../../en-US/skin/default/xml/rresult.xml?rt=list&" .
+	#		join('&', map { "rl=" . $_ } @$list) .
+	#		"\n\n";
+	#	return 302;
+	#}
 
-    my $search = 'record';
-    if ($rtype eq 'authority') {
-        $search = 'authority';
-    }
-    my $feed = create_record_feed( $search, $type, $list, $unapi, $org_unit->[0]->shortname, undef, $flesh_feed);
-    $feed->root($root);
+	my $search = 'record';
+	if ($rtype eq 'authority') {
+		$search = 'authority';
+	}
+	my $feed = create_record_feed( $search, $type, $list, $unapi, $org_unit->[0]->shortname, undef, $flesh_feed);
+	$feed->root($root);
 
-    if ($date) {
-        $feed->title("Up to $limit recent $rtype ${axis}s from $date forward");
-    } else {
-        $feed->title("$limit most recent $rtype ${axis}s");
-    }
+	if ($date) {
+		$feed->title("Up to $limit recent $rtype ${axis}s from $date forward");
+	} else {
+		$feed->title("$limit most recent $rtype ${axis}s");
+	}
 
-    $feed->creator($host);
-    $feed->update_ts();
+	$feed->creator($host);
+	$feed->update_ts();
 
-    $feed->link(alternate => $base . "/rss2-full/$rtype/$axis/$limit/$date" => 'application/rss+xml');
-    $feed->link(atom => $base . "/atom-full/$rtype/$axis/$limit/$date" => 'application/atom+xml');
-    $feed->link(html => $base . "/html-full/$rtype/$axis/$limit/$date" => 'text/html');
-    $feed->link(unapi => $unapi);
+	$feed->link(alternate => $base . "/rss2-full/$rtype/$axis/$limit/$date" => 'application/rss+xml');
+	$feed->link(atom => $base . "/atom-full/$rtype/$axis/$limit/$date" => 'application/atom+xml');
+	$feed->link(html => $base . "/html-full/$rtype/$axis/$limit/$date" => 'text/html');
+	$feed->link(unapi => $unapi);
 
-    $feed->link(
-        OPAC =>
-        "http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
-            join('&', map { 'rl=' . $_} @$list ),
-        'text/html'
-    );
+	$feed->link(
+		OPAC =>
+		"http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
+			join('&', map { 'rl=' . $_} @$list ),
+		'text/html'
+	);
 
 
-    print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
-    print $U->entityize($feed->toString) . "\n";
+	print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
+	print $U->entityize($feed->toString) . "\n";
 
-    return Apache2::Const::OK;
+	return Apache2::Const::OK;
 }
 
 sub opensearch_osd {
-    my $version = shift;
-    my $lib = shift;
-    my $class = shift;
-    my $base = shift;
+	my $version = shift;
+	my $lib = shift;
+	my $class = shift;
+	my $base = shift;
 
-    if ($version eq '1.0') {
-        print <<OSD;
+	if ($version eq '1.0') {
+		print <<OSD;
 Content-type: application/opensearchdescription+xml; charset=utf-8
 
 <?xml version="1.0" encoding="UTF-8"?>
@@ -1057,8 +1057,8 @@ Content-type: application/opensearchdescription+xml; charset=utf-8
   <AdultContent>false</AdultContent>
 </OpenSearchDescription>
 OSD
-    } else {
-        print <<OSD;
+	} else {
+		print <<OSD;
 Content-type: application/opensearchdescription+xml; charset=utf-8
 
 <?xml version="1.0" encoding="UTF-8"?>
@@ -1089,262 +1089,262 @@ Content-type: application/opensearchdescription+xml; charset=utf-8
   <InputEncoding>UTF-8</InputEncoding>
 </OpenSearchDescription>
 OSD
-    }
+	}
 
-    return Apache2::Const::OK;
+	return Apache2::Const::OK;
 }
 
 sub opensearch_feed {
-    my $apache = shift;
-    return Apache2::Const::DECLINED if (-e $apache->filename);
+	my $apache = shift;
+	return Apache2::Const::DECLINED if (-e $apache->filename);
+
+	my $cgi = new CGI;
+	my $year = (gmtime())[5] + 1900;
+
+	my $host = $cgi->virtual_host || $cgi->server_name;
+
+	my $add_path = 0;
+	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+		my $rel_name = $cgi->url(-relative=>1);
+		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+	}
 
-    my $cgi = new CGI;
-    my $year = (gmtime())[5] + 1900;
+	my $url = $cgi->url(-path_info=>$add_path);
+	my $root = (split 'opensearch', $url)[0];
+	my $base = (split 'opensearch', $url)[0] . 'opensearch';
+	my $unapi = (split 'opensearch', $url)[0] . 'unapi';
 
-    my $host = $cgi->virtual_host || $cgi->server_name;
-
-    my $add_path = 0;
-    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-        my $rel_name = $cgi->url(-relative=>1);
-        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-    }
-
-    my $url = $cgi->url(-path_info=>$add_path);
-    my $root = (split 'opensearch', $url)[0];
-    my $base = (split 'opensearch', $url)[0] . 'opensearch';
-    my $unapi = (split 'opensearch', $url)[0] . 'unapi';
-
-    my $path = $cgi->path_info;
-    #warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
-
-    if ($path =~ m{^/?(1\.\d{1})/(?:([^/]+)/)?([^/]+)/osd.xml}o) {
-        
-        my $version = $1;
-        my $lib = uc($2);
-        my $class = $3;
-
-        if (!$lib || $lib eq '-') {
-             $lib = $actor->request(
-                'open-ils.actor.org_unit_list.search' => parent_ou => undef
-            )->gather(1)->[0]->shortname;
-        }
-
-        if ($class eq '-') {
-            $class = 'keyword';
-        }
-
-        return opensearch_osd($version, $lib, $class, $base);
-    }
-
-
-    my $page = $cgi->param('startPage') || 1;
-    my $offset = $cgi->param('startIndex') || 1;
-    my $limit = $cgi->param('count') || 10;
-
-    $page = 1 if ($page !~ /^\d+$/);
-    $offset = 1 if ($offset !~ /^\d+$/);
-    $limit = 10 if ($limit !~ /^\d+$/); $limit = 25 if ($limit > 25);
-
-    if ($page > 1) {
-        $offset = ($page - 1) * $limit;
-    } else {
-        $offset -= 1;
-    }
-
-    my ($version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = ('','','','','','','','');
-    (undef,$version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = split '/', $path;
-
-    $lang = $cgi->param('searchLang') if $cgi->param('searchLang');
-    $lang = '' if ($lang eq '*');
-
-    $sort = $cgi->param('searchSort') if $cgi->param('searchSort');
-    $sort ||= '';
-    $sortdir = $cgi->param('searchSortDir') if $cgi->param('searchSortDir');
-    $sortdir ||= '';
-
-    $terms .= " " if ($terms && $cgi->param('searchTerms'));
-    $terms .= $cgi->param('searchTerms') if $cgi->param('searchTerms');
-
-    $class = $cgi->param('searchClass') if $cgi->param('searchClass');
-    $class ||= '-';
-
-    $type = $cgi->param('responseType') if $cgi->param('responseType');
-    $type ||= '-';
-
-    $org = $cgi->param('searchOrg') if $cgi->param('searchOrg');
-    $org ||= '-';
-
-
-    my $kwt = $cgi->param('kw');
-    my $tit = $cgi->param('ti');
-    my $aut = $cgi->param('au');
-    my $sut = $cgi->param('su');
-    my $set = $cgi->param('se');
-
-    $terms .= " " if ($terms && $kwt);
-    $terms .= "keyword: $kwt" if ($kwt);
-    $terms .= " " if ($terms && $tit);
-    $terms .= "title: $tit" if ($tit);
-    $terms .= " " if ($terms && $aut);
-    $terms .= "author: $aut" if ($aut);
-    $terms .= " " if ($terms && $sut);
-    $terms .= "subject: $sut" if ($sut);
-    $terms .= " " if ($terms && $set);
-    $terms .= "series: $set" if ($set);
-
-    if ($version eq '1.0') {
-        $type = 'rss2';
-    } elsif ($type eq '-') {
-        $type = 'atom';
-    }
-    my $flesh_feed = parse_feed_type($type);
-
-    $terms = decode_utf8($terms);
-    $lang = 'eng' if ($lang eq 'en-US');
-
-    $log->debug("OpenSearch terms: $terms");
-
-    my $org_unit = get_ou($org);
-
-    # Apostrophes break search and get indexed as spaces anyway
-    my $safe_terms = $terms;
-    $safe_terms =~ s{'}{ }go;
-
-    my $recs = $search->request(
-        'open-ils.search.biblio.multiclass.query' => {
-            org_unit    => $org_unit->[0]->id,
-            offset        => $offset,
-            limit        => $limit,
-            sort        => $sort,
-            sort_dir    => $sortdir,
+	my $path = $cgi->path_info;
+	#warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
+
+	if ($path =~ m{^/?(1\.\d{1})/(?:([^/]+)/)?([^/]+)/osd.xml}o) {
+		
+		my $version = $1;
+		my $lib = uc($2);
+		my $class = $3;
+
+		if (!$lib || $lib eq '-') {
+		 	$lib = $actor->request(
+				'open-ils.actor.org_unit_list.search' => parent_ou => undef
+			)->gather(1)->[0]->shortname;
+		}
+
+		if ($class eq '-') {
+			$class = 'keyword';
+		}
+
+		return opensearch_osd($version, $lib, $class, $base);
+	}
+
+
+	my $page = $cgi->param('startPage') || 1;
+	my $offset = $cgi->param('startIndex') || 1;
+	my $limit = $cgi->param('count') || 10;
+
+	$page = 1 if ($page !~ /^\d+$/);
+	$offset = 1 if ($offset !~ /^\d+$/);
+	$limit = 10 if ($limit !~ /^\d+$/); $limit = 25 if ($limit > 25);
+
+	if ($page > 1) {
+		$offset = ($page - 1) * $limit;
+	} else {
+		$offset -= 1;
+	}
+
+	my ($version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = ('','','','','','','','');
+	(undef,$version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = split '/', $path;
+
+	$lang = $cgi->param('searchLang') if $cgi->param('searchLang');
+	$lang = '' if ($lang eq '*');
+
+	$sort = $cgi->param('searchSort') if $cgi->param('searchSort');
+	$sort ||= '';
+	$sortdir = $cgi->param('searchSortDir') if $cgi->param('searchSortDir');
+	$sortdir ||= '';
+
+	$terms .= " " if ($terms && $cgi->param('searchTerms'));
+	$terms .= $cgi->param('searchTerms') if $cgi->param('searchTerms');
+
+	$class = $cgi->param('searchClass') if $cgi->param('searchClass');
+	$class ||= '-';
+
+	$type = $cgi->param('responseType') if $cgi->param('responseType');
+	$type ||= '-';
+
+	$org = $cgi->param('searchOrg') if $cgi->param('searchOrg');
+	$org ||= '-';
+
+
+	my $kwt = $cgi->param('kw');
+	my $tit = $cgi->param('ti');
+	my $aut = $cgi->param('au');
+	my $sut = $cgi->param('su');
+	my $set = $cgi->param('se');
+
+	$terms .= " " if ($terms && $kwt);
+	$terms .= "keyword: $kwt" if ($kwt);
+	$terms .= " " if ($terms && $tit);
+	$terms .= "title: $tit" if ($tit);
+	$terms .= " " if ($terms && $aut);
+	$terms .= "author: $aut" if ($aut);
+	$terms .= " " if ($terms && $sut);
+	$terms .= "subject: $sut" if ($sut);
+	$terms .= " " if ($terms && $set);
+	$terms .= "series: $set" if ($set);
+
+	if ($version eq '1.0') {
+		$type = 'rss2';
+	} elsif ($type eq '-') {
+		$type = 'atom';
+	}
+	my $flesh_feed = parse_feed_type($type);
+
+	$terms = decode_utf8($terms);
+	$lang = 'eng' if ($lang eq 'en-US');
+
+	$log->debug("OpenSearch terms: $terms");
+
+	my $org_unit = get_ou($org);
+
+	# Apostrophes break search and get indexed as spaces anyway
+	my $safe_terms = $terms;
+	$safe_terms =~ s{'}{ }go;
+
+	my $recs = $search->request(
+		'open-ils.search.biblio.multiclass.query' => {
+			org_unit	=> $org_unit->[0]->id,
+			offset		=> $offset,
+			limit		=> $limit,
+			sort		=> $sort,
+			sort_dir	=> $sortdir,
             default_class => $class,
-            ($lang ?    ( 'language' => $lang    ) : ()),
-        } => $safe_terms => 1
-    )->gather(1);
-
-    $log->debug("Hits for [$terms]: $recs->{count}");
-
-    my $feed = create_record_feed(
-        'record',
-        $type,
-        [ map { $_->[0] } @{$recs->{ids}} ],
-        $unapi,
-        $org,
-        undef,
-        $flesh_feed
-    );
-
-    $log->debug("Feed created...");
-
-    $feed->root($root);
-    $feed->lib($org);
-    $feed->search($safe_terms);
-    $feed->class($class);
-
-    $feed->title("Search results for [$terms] at ".$org_unit->[0]->name);
-
-    $feed->creator($host);
-    $feed->update_ts();
-
-    $feed->_create_node(
-        $feed->{item_xpath},
-        'http://a9.com/-/spec/opensearch/1.1/',
-        'totalResults',
-        $recs->{count},
-    );
-
-    $feed->_create_node(
-        $feed->{item_xpath},
-        'http://a9.com/-/spec/opensearch/1.1/',
-        'startIndex',
-        $offset + 1,
-    );
-
-    $feed->_create_node(
-        $feed->{item_xpath},
-        'http://a9.com/-/spec/opensearch/1.1/',
-        'itemsPerPage',
-        $limit,
-    );
-
-    $log->debug("...basic feed data added...");
-
-    $feed->link(
-        next =>
-        $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int($offset + $limit + 1) . "&count=" . $limit =>
-        'application/opensearch+xml'
-    ) if ($offset + $limit < $recs->{count});
-
-    $feed->link(
-        previous =>
-        $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int(($offset - $limit) + 1) . "&count=" . $limit =>
-        'application/opensearch+xml'
-    ) if ($offset);
-
-    $feed->link(
-        self =>
-        $base .  "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
-        'application/opensearch+xml'
-    );
-
-    $feed->link(
-        alternate =>
-        $base .  "/$version/$org/rss2-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
-        'application/rss+xml'
-    );
-
-    $feed->link(
-        atom =>
-        $base .  "/$version/$org/atom-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
-        'application/atom+xml'
-    );
-
-    $feed->link(
-        'html' =>
-        $base .  "/$version/$org/html/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
-        'text/html'
-    );
-
-    $feed->link(
-        'html-full' =>
-        $base .  "/$version/$org/html-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
-        'text/html'
-    );
-
-    $feed->link( 'unapi-server' => $unapi);
-
-    $log->debug("...feed links added...");
-
-#    $feed->link(
-#        opac =>
-#        $root . "../$lang/skin/default/xml/rresult.xml?rt=list&" .
-#            join('&', map { 'rl=' . $_->[0] } grep { ref $_ && defined $_->[0] } @{$recs->{ids}} ),
-#        'text/html'
-#    );
-
-    #print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . entityize($feed->toString) . "\n";
-    print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . $feed->toString . "\n";
-
-    $log->debug("...and feed returned.");
-
-    return Apache2::Const::OK;
+			($lang ?    ( 'language' => $lang    ) : ()),
+		} => $safe_terms => 1
+	)->gather(1);
+
+	$log->debug("Hits for [$terms]: $recs->{count}");
+
+	my $feed = create_record_feed(
+		'record',
+		$type,
+		[ map { $_->[0] } @{$recs->{ids}} ],
+		$unapi,
+		$org,
+		undef,
+		$flesh_feed
+	);
+
+	$log->debug("Feed created...");
+
+	$feed->root($root);
+	$feed->lib($org);
+	$feed->search($safe_terms);
+	$feed->class($class);
+
+	$feed->title("Search results for [$terms] at ".$org_unit->[0]->name);
+
+	$feed->creator($host);
+	$feed->update_ts();
+
+	$feed->_create_node(
+		$feed->{item_xpath},
+		'http://a9.com/-/spec/opensearch/1.1/',
+		'totalResults',
+		$recs->{count},
+	);
+
+	$feed->_create_node(
+		$feed->{item_xpath},
+		'http://a9.com/-/spec/opensearch/1.1/',
+		'startIndex',
+		$offset + 1,
+	);
+
+	$feed->_create_node(
+		$feed->{item_xpath},
+		'http://a9.com/-/spec/opensearch/1.1/',
+		'itemsPerPage',
+		$limit,
+	);
+
+	$log->debug("...basic feed data added...");
+
+	$feed->link(
+		next =>
+		$base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int($offset + $limit + 1) . "&count=" . $limit =>
+		'application/opensearch+xml'
+	) if ($offset + $limit < $recs->{count});
+
+	$feed->link(
+		previous =>
+		$base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int(($offset - $limit) + 1) . "&count=" . $limit =>
+		'application/opensearch+xml'
+	) if ($offset);
+
+	$feed->link(
+		self =>
+		$base .  "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
+		'application/opensearch+xml'
+	);
+
+	$feed->link(
+		alternate =>
+		$base .  "/$version/$org/rss2-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
+		'application/rss+xml'
+	);
+
+	$feed->link(
+		atom =>
+		$base .  "/$version/$org/atom-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
+		'application/atom+xml'
+	);
+
+	$feed->link(
+		'html' =>
+		$base .  "/$version/$org/html/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
+		'text/html'
+	);
+
+	$feed->link(
+		'html-full' =>
+		$base .  "/$version/$org/html-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
+		'text/html'
+	);
+
+	$feed->link( 'unapi-server' => $unapi);
+
+	$log->debug("...feed links added...");
+
+#	$feed->link(
+#		opac =>
+#		$root . "../$lang/skin/default/xml/rresult.xml?rt=list&" .
+#			join('&', map { 'rl=' . $_->[0] } grep { ref $_ && defined $_->[0] } @{$recs->{ids}} ),
+#		'text/html'
+#	);
+
+	#print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . entityize($feed->toString) . "\n";
+	print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . $feed->toString . "\n";
+
+	$log->debug("...and feed returned.");
+
+	return Apache2::Const::OK;
 }
 
 sub create_record_feed {
-    my $search = shift;
-    my $type = shift;
-    my $records = shift;
-    my $unapi = shift;
+	my $search = shift;
+	my $type = shift;
+	my $records = shift;
+	my $unapi = shift;
 
-    my $lib = uc(shift()) || '-';
-    my $depth = shift;
-    my $flesh = shift;
+	my $lib = uc(shift()) || '-';
+	my $depth = shift;
+	my $flesh = shift;
 
-    my $paging = shift;
+	my $paging = shift;
 
-    my $cgi = new CGI;
-    my $base = $cgi->url;
-    my $host = $cgi->virtual_host || $cgi->server_name;
+	my $cgi = new CGI;
+	my $base = $cgi->url;
+	my $host = $cgi->virtual_host || $cgi->server_name;
 
     my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
     $year += 1900;
@@ -1352,253 +1352,253 @@ sub create_record_feed {
 
     my $tag_prefix = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d", $month, $day);
 
-    my $flesh_feed = defined($flesh) ? $flesh : parse_feed_type($type);
+	my $flesh_feed = defined($flesh) ? $flesh : parse_feed_type($type);
 
-    $type =~ s/(-full|-uris)$//o;
+	$type =~ s/(-full|-uris)$//o;
 
-    my $feed = new OpenILS::WWW::SuperCat::Feed ($type);
-    $feed->base($base) if ($flesh);
-    $feed->unapi($unapi) if ($flesh);
+	my $feed = new OpenILS::WWW::SuperCat::Feed ($type);
+	$feed->base($base) if ($flesh);
+	$feed->unapi($unapi) if ($flesh);
 
-    $type = 'atom' if ($type eq 'html');
-    $type = 'marcxml' if (($type eq 'htmlholdings') || ($type eq 'marctxt') || ($type eq 'ris'));
+	$type = 'atom' if ($type eq 'html');
+	$type = 'marcxml' if (($type eq 'htmlholdings') || ($type eq 'marctxt') || ($type eq 'ris'));
 
-    #$records = $supercat->request( "open-ils.supercat.record.object.retrieve", $records )->gather(1);
+	#$records = $supercat->request( "open-ils.supercat.record.object.retrieve", $records )->gather(1);
 
-    my $count = 0;
-    for my $record (@$records) {
-        next unless($record);
+	my $count = 0;
+	for my $record (@$records) {
+		next unless($record);
 
-        #my $rec = $record->id;
-        my $rec = $record;
+		#my $rec = $record->id;
+		my $rec = $record;
 
-        my $item_tag = "$tag_prefix:biblio-record_entry/$rec/$lib";
-        $item_tag = "$tag_prefix:metabib-metarecord/$rec/$lib" if ($search eq 'metarecord');
-        $item_tag = "$tag_prefix:isbn/$rec/$lib" if ($search eq 'isbn');
-        $item_tag .= "/$depth" if (defined($depth));
+		my $item_tag = "$tag_prefix:biblio-record_entry/$rec/$lib";
+		$item_tag = "$tag_prefix:metabib-metarecord/$rec/$lib" if ($search eq 'metarecord');
+		$item_tag = "$tag_prefix:isbn/$rec/$lib" if ($search eq 'isbn');
+		$item_tag .= "/$depth" if (defined($depth));
 
-        $item_tag = "$tag_prefix:authority-record_entry/$rec" if ($search eq 'authority');
+		$item_tag = "$tag_prefix:authority-record_entry/$rec" if ($search eq 'authority');
 
-        my $xml = $supercat->request(
-            "open-ils.supercat.$search.$type.retrieve",
-            $rec
-        )->gather(1);
-        next unless $xml;
+		my $xml = $supercat->request(
+			"open-ils.supercat.$search.$type.retrieve",
+			$rec
+		)->gather(1);
+		next unless $xml;
 
-        my $node = $feed->add_item($xml);
-        next unless $node;
+		my $node = $feed->add_item($xml);
+		next unless $node;
 
-        $xml = '';
-        if ($lib && ($type eq 'marcxml' || $type eq 'atom') &&  $flesh > 0) {
-            my $r = $supercat->request( "open-ils.supercat.$search.holdings_xml.retrieve", $rec, $lib, $depth, $flesh_feed, $paging );
-            while ( !$r->complete ) {
-                $xml .= join('', map {$_->content} $r->recv);
-            }
-            $xml .= join('', map {$_->content} $r->recv);
-            $node->add_holdings($xml);
-        }
+		$xml = '';
+		if ($lib && ($type eq 'marcxml' || $type eq 'atom') &&  $flesh > 0) {
+			my $r = $supercat->request( "open-ils.supercat.$search.holdings_xml.retrieve", $rec, $lib, $depth, $flesh_feed, $paging );
+			while ( !$r->complete ) {
+				$xml .= join('', map {$_->content} $r->recv);
+			}
+			$xml .= join('', map {$_->content} $r->recv);
+			$node->add_holdings($xml);
+		}
 
-        $node->id($item_tag);
-        #$node->update_ts(cleanse_ISO8601($record->edit_date));
-        $node->link(alternate => $feed->unapi . "?id=$item_tag&format=htmlholdings-full" => 'text/html') if ($flesh > 0);
-        $node->link(opac => $feed->unapi . "?id=$item_tag&format=opac") if ($flesh > 0);
-        $node->link(unapi => $feed->unapi . "?id=$item_tag") if ($flesh);
-        $node->link('unapi-id' => $item_tag) if ($flesh);
-    }
+		$node->id($item_tag);
+		#$node->update_ts(cleanse_ISO8601($record->edit_date));
+		$node->link(alternate => $feed->unapi . "?id=$item_tag&format=htmlholdings-full" => 'text/html') if ($flesh > 0);
+		$node->link(opac => $feed->unapi . "?id=$item_tag&format=opac") if ($flesh > 0);
+		$node->link(unapi => $feed->unapi . "?id=$item_tag") if ($flesh);
+		$node->link('unapi-id' => $item_tag) if ($flesh);
+	}
 
-    return $feed;
+	return $feed;
 }
 
 sub string_browse {
-    my $apache = shift;
-    return Apache2::Const::DECLINED if (-e $apache->filename);
+	my $apache = shift;
+	return Apache2::Const::DECLINED if (-e $apache->filename);
 
-    my $cgi = new CGI;
-    my $year = (gmtime())[5] + 1900;
+	my $cgi = new CGI;
+	my $year = (gmtime())[5] + 1900;
 
-    my $host = $cgi->virtual_host || $cgi->server_name;
+	my $host = $cgi->virtual_host || $cgi->server_name;
 
-    my $add_path = 0;
-    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-        my $rel_name = $cgi->url(-relative=>1);
-        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-    }
+	my $add_path = 0;
+	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+		my $rel_name = $cgi->url(-relative=>1);
+		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+	}
 
-    my $url = $cgi->url(-path_info=>$add_path);
-    my $root = (split 'browse', $url)[0];
-    my $base = (split 'browse', $url)[0] . 'browse';
-    my $unapi = (split 'browse', $url)[0] . 'unapi';
+	my $url = $cgi->url(-path_info=>$add_path);
+	my $root = (split 'browse', $url)[0];
+	my $base = (split 'browse', $url)[0] . 'browse';
+	my $unapi = (split 'browse', $url)[0] . 'unapi';
 
-    my $path = $cgi->path_info;
-    $path =~ s/^\///og;
+	my $path = $cgi->path_info;
+	$path =~ s/^\///og;
 
-    my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
-    #warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
+	my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
+	#warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
 
     return item_age_browse($apache) if ($axis eq 'item-age'); # short-circut to the item-age sub
 
-    my $status = [$cgi->param('status')];
-    my $cpLoc = [$cgi->param('copyLocation')];
-    $site ||= $cgi->param('searchOrg');
-    $page ||= $cgi->param('startPage') || 0;
-    $page_size ||= $cgi->param('count') || 9;
-
-    $page = 0 if ($page !~ /^-?\d+$/);
-    $page_size = 9 if $page_size !~ /^\d+$/;
-
-    my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
-    my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
-
-    unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
-        warn "something's wrong...";
-        warn " >>> format: $format -> axis: $axis -> site: $site -> string: $string -> page: $page -> page_size: $page_size ";
-        return undef;
-    }
-
-    $string = decode_utf8($string);
-    $string =~ s/\+/ /go;
-    $string =~ s/'//go;
-
-    my $tree = $supercat->request(
-        "open-ils.supercat.$axis.browse",
-        $string,
-        (($axis =~ /^authority/) ? () : ($site)),
-        $page_size,
-        $page,
-        $status,
-        $cpLoc
-    )->gather(1);
+	my $status = [$cgi->param('status')];
+	my $cpLoc = [$cgi->param('copyLocation')];
+	$site ||= $cgi->param('searchOrg');
+	$page ||= $cgi->param('startPage') || 0;
+	$page_size ||= $cgi->param('count') || 9;
+
+	$page = 0 if ($page !~ /^-?\d+$/);
+	$page_size = 9 if $page_size !~ /^\d+$/;
+
+	my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
+	my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
+
+	unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
+		warn "something's wrong...";
+		warn " >>> format: $format -> axis: $axis -> site: $site -> string: $string -> page: $page -> page_size: $page_size ";
+		return undef;
+	}
+
+	$string = decode_utf8($string);
+	$string =~ s/\+/ /go;
+	$string =~ s/'//go;
+
+	my $tree = $supercat->request(
+		"open-ils.supercat.$axis.browse",
+		$string,
+		(($axis =~ /^authority/) ? () : ($site)),
+		$page_size,
+		$page,
+		$status,
+		$cpLoc
+	)->gather(1);
 
     (my $norm_format = $format) =~ s/(-full|-uris)$//o;
 
-    my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
-    print $header.$content;
-    return Apache2::Const::OK;
+	my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
+	print $header.$content;
+	return Apache2::Const::OK;
 }
 
 sub string_startwith {
-    my $apache = shift;
-    return Apache2::Const::DECLINED if (-e $apache->filename);
-
-    my $cgi = new CGI;
-    my $year = (gmtime())[5] + 1900;
-
-    my $host = $cgi->virtual_host || $cgi->server_name;
-
-    my $add_path = 0;
-    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-        my $rel_name = $cgi->url(-relative=>1);
-        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-    }
-
-    my $url = $cgi->url(-path_info=>$add_path);
-    my $root = (split 'startwith', $url)[0];
-    my $base = (split 'startwith', $url)[0] . 'startwith';
-    my $unapi = (split 'startwith', $url)[0] . 'unapi';
-
-    my $path = $cgi->path_info;
-    $path =~ s/^\///og;
-
-    my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
-    #warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
-
-    my $status = [$cgi->param('status')];
-    my $cpLoc = [$cgi->param('copyLocation')];
-    $site ||= $cgi->param('searchOrg');
-    $page ||= $cgi->param('startPage') || 0;
-    $page_size ||= $cgi->param('count') || 9;
-
-    $page = 0 if ($page !~ /^-?\d+$/);
-    $page_size = 9 if $page_size !~ /^\d+$/;
-
-    my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
-    my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
-
-    unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
-        warn "something's wrong...";
-        warn " >>> format: $format -> axis: $axis -> site: $site -> string: $string -> page: $page -> page_size: $page_size ";
-        return undef;
-    }
-
-    $string = decode_utf8($string);
-    $string =~ s/\+/ /go;
-    $string =~ s/'//go;
-
-    my $tree = $supercat->request(
-        "open-ils.supercat.$axis.startwith",
-        $string,
-        (($axis =~ /^authority/) ? () : ($site)),
-        $page_size,
-        $page,
-        $status,
-        $cpLoc
-    )->gather(1);
+	my $apache = shift;
+	return Apache2::Const::DECLINED if (-e $apache->filename);
+
+	my $cgi = new CGI;
+	my $year = (gmtime())[5] + 1900;
+
+	my $host = $cgi->virtual_host || $cgi->server_name;
+
+	my $add_path = 0;
+	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+		my $rel_name = $cgi->url(-relative=>1);
+		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+	}
+
+	my $url = $cgi->url(-path_info=>$add_path);
+	my $root = (split 'startwith', $url)[0];
+	my $base = (split 'startwith', $url)[0] . 'startwith';
+	my $unapi = (split 'startwith', $url)[0] . 'unapi';
+
+	my $path = $cgi->path_info;
+	$path =~ s/^\///og;
+
+	my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
+	#warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
+
+	my $status = [$cgi->param('status')];
+	my $cpLoc = [$cgi->param('copyLocation')];
+	$site ||= $cgi->param('searchOrg');
+	$page ||= $cgi->param('startPage') || 0;
+	$page_size ||= $cgi->param('count') || 9;
+
+	$page = 0 if ($page !~ /^-?\d+$/);
+	$page_size = 9 if $page_size !~ /^\d+$/;
+
+	my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
+	my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
+
+	unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
+		warn "something's wrong...";
+		warn " >>> format: $format -> axis: $axis -> site: $site -> string: $string -> page: $page -> page_size: $page_size ";
+		return undef;
+	}
+
+	$string = decode_utf8($string);
+	$string =~ s/\+/ /go;
+	$string =~ s/'//go;
+
+	my $tree = $supercat->request(
+		"open-ils.supercat.$axis.startwith",
+		$string,
+		(($axis =~ /^authority/) ? () : ($site)),
+		$page_size,
+		$page,
+		$status,
+		$cpLoc
+	)->gather(1);
 
     (my $norm_format = $format) =~ s/(-full|-uris)$//o;
 
-    my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
-    print $header.$content;
-    return Apache2::Const::OK;
+	my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
+	print $header.$content;
+	return Apache2::Const::OK;
 }
 
 sub item_age_browse {
-    my $apache = shift;
-    return Apache2::Const::DECLINED if (-e $apache->filename);
-
-    my $cgi = new CGI;
-    my $year = (gmtime())[5] + 1900;
-
-    my $host = $cgi->virtual_host || $cgi->server_name;
-
-    my $add_path = 0;
-    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-        my $rel_name = $cgi->url(-relative=>1);
-        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-    }
-
-    my $url = $cgi->url(-path_info=>$add_path);
-    my $root = (split 'browse', $url)[0];
-    my $base = (split 'browse', $url)[0] . 'browse';
-    my $unapi = (split 'browse', $url)[0] . 'unapi';
-
-    my $path = $cgi->path_info;
-    $path =~ s/^\///og;
-
-    my ($format,$axis,$site,$page,$page_size) = split '/', $path;
-    #warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
-
-    unless ($axis eq 'item-age') {
-        warn "something's wrong...";
-        warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
-        return undef;
-    }
-
-    my $status = [$cgi->param('status')];
-    my $cpLoc = [$cgi->param('copyLocation')];
-    $site ||= $cgi->param('searchOrg') || '-';
-    $page ||= $cgi->param('startPage') || 1;
-    $page_size ||= $cgi->param('count') || 10;
-
-    $page = 1 if ($page !~ /^-?\d+$/ || $page < 1);
-    $page_size = 10 if $page_size !~ /^\d+$/;
-
-    my $prev = join('/', $base,$format,$axis,$site,$page - 1,$page_size);
-    my $next = join('/', $base,$format,$axis,$site,$page + 1,$page_size);
-
-    my $recs = $supercat->request(
-        "open-ils.supercat.new_book_list",
-        $site,
-        $page_size,
-        $page,
-        $status,
-        $cpLoc
-    )->gather(1);
+	my $apache = shift;
+	return Apache2::Const::DECLINED if (-e $apache->filename);
+
+	my $cgi = new CGI;
+	my $year = (gmtime())[5] + 1900;
+
+	my $host = $cgi->virtual_host || $cgi->server_name;
+
+	my $add_path = 0;
+	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+		my $rel_name = $cgi->url(-relative=>1);
+		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+	}
+
+	my $url = $cgi->url(-path_info=>$add_path);
+	my $root = (split 'browse', $url)[0];
+	my $base = (split 'browse', $url)[0] . 'browse';
+	my $unapi = (split 'browse', $url)[0] . 'unapi';
+
+	my $path = $cgi->path_info;
+	$path =~ s/^\///og;
+
+	my ($format,$axis,$site,$page,$page_size) = split '/', $path;
+	#warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
+
+	unless ($axis eq 'item-age') {
+		warn "something's wrong...";
+		warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
+		return undef;
+	}
+
+	my $status = [$cgi->param('status')];
+	my $cpLoc = [$cgi->param('copyLocation')];
+	$site ||= $cgi->param('searchOrg') || '-';
+	$page ||= $cgi->param('startPage') || 1;
+	$page_size ||= $cgi->param('count') || 10;
+
+	$page = 1 if ($page !~ /^-?\d+$/ || $page < 1);
+	$page_size = 10 if $page_size !~ /^\d+$/;
+
+	my $prev = join('/', $base,$format,$axis,$site,$page - 1,$page_size);
+	my $next = join('/', $base,$format,$axis,$site,$page + 1,$page_size);
+
+	my $recs = $supercat->request(
+		"open-ils.supercat.new_book_list",
+		$site,
+		$page_size,
+		$page,
+		$status,
+		$cpLoc
+	)->gather(1);
 
     (my $norm_format = $format) =~ s/(-full|-uris)$//o;
 
-    my ($header,$content) = $browse_types{$axis}{$norm_format}->($recs,$prev,$next,$format,$unapi,$base,$site);
-    print $header.$content;
-    return Apache2::Const::OK;
+	my ($header,$content) = $browse_types{$axis}{$norm_format}->($recs,$prev,$next,$format,$unapi,$base,$site);
+	print $header.$content;
+	return Apache2::Const::OK;
 }
 
 our %qualifier_map = (
@@ -1687,91 +1687,91 @@ our %qualifier_map = (
 );
 
 our %qualifier_ids = (
-    eg  => 'http://open-ils.org/spec/SRU/context-set/evergreen/v1',
-    dc  => 'info:srw/cql-context-set/1/dc-v1.1',
-    bib => 'info:srw/cql-context-set/1/bib-v1.0',
-    srw => ''
+		eg => 'http://open-ils.org/spec/SRU/context-set/evergreen/v1',
+		dc => 'info:srw/cql-context-set/1/dc-v1.1',
+		bib => 'info:srw/cql-context-set/1/bib-v1.0',
+		srw	=> ''
 );
 
 our %nested_qualifier_map = (
-        eg => {
-            site        => ['site','Evergreen Site Code (shortname)'],
-            sort        => ['sort','Sort on relevance, title, author, pubdate, create_date or edit_date'],
-            direction   => ['dir','Sort direction (asc|desc)'],
-            available   => ['available','Filter to available (true|false)'],
-            title       => ['title'],
-            author      => ['author'],
-            name        => ['author'],
-            subject     => ['subject'],
-            keyword     => ['keyword'],
-            series      => ['series'],
-        },
-        dc => {
-            title       => ['title'],
-            creator     => ['author'],
-            contributor => ['author'],
-            publisher   => ['keyword'],
-            subject     => ['subject'],
-            identifier  => ['keyword'],
-            type        => [undef],
-            format      => [undef],
-            language    => ['lang'],
-        },
-        bib => {
-        # Title class:
-            titleAbbreviated    => ['title'],
-            titleUniform        => ['title'],
-            titleTranslated     => ['title'],
-            titleAlternative    => ['title'],
-            titleSeries         => ['series'],
+		eg => {
+			site		=> ['site','Evergreen Site Code (shortname)'],
+			sort		=> ['sort','Sort on relevance, title, author, pubdate, create_date or edit_date'],
+			direction	=> ['dir','Sort direction (asc|desc)'],
+			available	=> ['available','Filter to available (true|false)'],
+			title		=> ['title'],
+			author		=> ['author'],
+			name		=> ['author'],
+			subject		=> ['subject'],
+			keyword		=> ['keyword'],
+			series		=> ['series'],
+		},
+		dc => {
+			title		=> ['title'],
+			creator		=> ['author'],
+			contributor	=> ['author'],
+			publisher	=> ['keyword'],
+			subject		=> ['subject'],
+			identifier	=> ['keyword'],
+			type		=> [undef],
+			format		=> [undef],
+			language	=> ['lang'],
+		},
+		bib => {
+		# Title class:
+	        titleAbbreviated	=> ['title'],
+		    titleUniform		=> ['title'],
+			titleTranslated		=> ['title'],
+	        titleAlternative	=> ['title'],
+		    titleSeries			=> ['series'],
 
     # Author/Name class:
-            name                => ['author'],
-            namePersonal        => ['author'],
-            namePersonalFamily  => ['author'],
-            namePersonalGiven   => ['author'],
-            nameCorporate       => ['author'],
-            nameConference      => ['author'],
-
-        # Subject class:
-            subjectPlace        => ['subject'],
-            subjectTitle        => ['keyword'],
-            subjectName         => ['subject|name'],
-            subjectOccupation   => ['keyword'],
+			name				=> ['author'],
+			namePersonal		=> ['author'],
+			namePersonalFamily	=> ['author'],
+			namePersonalGiven	=> ['author'],
+			nameCorporate		=> ['author'],
+			nameConference		=> ['author'],
+
+		# Subject class:
+			subjectPlace		=> ['subject'],
+			subjectTitle		=> ['keyword'],
+			subjectName			=> ['subject|name'],
+			subjectOccupation	=> ['keyword'],
 
     # Keyword class:
 
     # Dates:
-            dateIssued          => [undef],
-            dateCreated         => [undef],
-            dateValid           => [undef],
-            dateModified        => [undef],
-            dateCopyright       => [undef],
+			dateIssued			=> [undef],
+			dateCreated			=> [undef],
+			dateValid			=> [undef],
+			dateModified		=> [undef],
+			dateCopyright		=> [undef],
 
     # Genre:
-            genre               => ['keyword'],
+			genre				=> ['keyword'],
 
     # Target Audience:
-            audience            => [undef],
+			audience			=> [undef],
 
     # Place of Origin:
-            originPlace         => [undef],
+			originPlace			=> [undef],
 
     # Edition
-            edition             => ['keyword'],
+			edition				=> ['keyword'],
 
     # Part:
-            volume              => ['keyword'],
-            issue               => ['keyword'],
-            startPage           => ['keyword'],
-            endPage             => ['keyword'],
+			volume				=> ['keyword'],
+			issue				=> ['keyword'],
+			startPage			=> ['keyword'],
+			endPage				=> ['keyword'],
 
     # Issuance:
-            issuance            => ['keyword'],
-        },
-        srw    => {
-            serverChoice        => ['keyword'],
-        },
+			issuance			=> ['keyword'],
+		},
+		srw	=> {
+			serverChoice		=> ['keyword'],
+		},
 );
 
 # Our authority search options are currently pretty impoverished;
@@ -1789,49 +1789,49 @@ our %nested_auth_qualifier_map = (
 
 my $base_explain = <<XML;
 <explain
-        id="evergreen-sru-explain-full"
-        authoritative="true"
-        xmlns:z="http://explain.z3950.org/dtd/2.0/"
-        xmlns="http://explain.z3950.org/dtd/2.0/">
-    <serverInfo transport="http" protocol="SRU" version="1.1">
-        <host/>
-        <port/>
-        <database/>
-    </serverInfo>
-
-    <databaseInfo>
-        <title primary="true"/>
-        <description primary="true"/>
-    </databaseInfo>
-
-    <indexInfo>
-        <set identifier="info:srw/cql-context-set/1/cql-v1.2" name="cql"/>
-    </indexInfo>
-
-    <schemaInfo>
-        <schema
-                identifier="info:srw/schema/1/marcxml-v1.1"
-                location="http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
-                sort="true"
-                retrieve="true"
-                name="marcxml">
-            <title>MARC21Slim (marcxml)</title>
-        </schema>
-    </schemaInfo>
-
-    <configInfo>
-        <default type="numberOfRecords">10</default>
-        <default type="contextSet">eg</default>
-        <default type="index">keyword</default>
-        <default type="relation">all</default>
-        <default type="sortSchema">marcxml</default>
-        <default type="retrieveSchema">marcxml</default>
-        <setting type="maximumRecords">50</setting>
-        <supports type="relationModifier">relevant</supports>
-        <supports type="relationModifier">stem</supports>
-        <supports type="relationModifier">fuzzy</supports>
-        <supports type="relationModifier">word</supports>
-    </configInfo>
+		id="evergreen-sru-explain-full"
+		authoritative="true"
+		xmlns:z="http://explain.z3950.org/dtd/2.0/"
+		xmlns="http://explain.z3950.org/dtd/2.0/">
+	<serverInfo transport="http" protocol="SRU" version="1.1">
+		<host/>
+		<port/>
+		<database/>
+	</serverInfo>
+
+	<databaseInfo>
+		<title primary="true"/>
+		<description primary="true"/>
+	</databaseInfo>
+
+	<indexInfo>
+		<set identifier="info:srw/cql-context-set/1/cql-v1.2" name="cql"/>
+	</indexInfo>
+
+	<schemaInfo>
+		<schema
+				identifier="info:srw/schema/1/marcxml-v1.1"
+				location="http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
+				sort="true"
+				retrieve="true"
+				name="marcxml">
+			<title>MARC21Slim (marcxml)</title>
+		</schema>
+	</schemaInfo>
+
+	<configInfo>
+		<default type="numberOfRecords">10</default>
+		<default type="contextSet">eg</default>
+		<default type="index">keyword</default>
+		<default type="relation">all</default>
+		<default type="sortSchema">marcxml</default>
+		<default type="retrieveSchema">marcxml</default>
+		<setting type="maximumRecords">50</setting>
+		<supports type="relationModifier">relevant</supports>
+		<supports type="relationModifier">stem</supports>
+		<supports type="relationModifier">fuzzy</supports>
+		<supports type="relationModifier">word</supports>
+	</configInfo>
 
 </explain>
 XML
@@ -1839,29 +1839,29 @@ XML
 
 my $ex_doc;
 sub sru_search {
-    my $cgi = new CGI;
+	my $cgi = new CGI;
 
-    my $req = SRU::Request->newFromCGI( $cgi );
-    my $resp = SRU::Response->newFromRequest( $req );
+	my $req = SRU::Request->newFromCGI( $cgi );
+	my $resp = SRU::Response->newFromRequest( $req );
 
-    # Find the org_unit shortname, if passed as part of the URL
-    # http://example.com/opac/extras/sru/SHORTNAME
-    my $url = $cgi->path_info;
-    my ($shortname, $holdings) = $url =~ m#/?([^/]*)(/holdings)?#;
+	# Find the org_unit shortname, if passed as part of the URL
+	# http://example.com/opac/extras/sru/SHORTNAME
+	my $url = $cgi->path_info;
+	my ($shortname, $holdings) = $url =~ m#/?([^/]*)(/holdings)?#;
 
-    if ( $resp->type eq 'searchRetrieve' ) {
+	if ( $resp->type eq 'searchRetrieve' ) {
 
-        # Older versions of Debian packages returned terms to us double-encoded,
-        # so we had to forcefully double-decode them a second time with
-        # an outer decode('utf8', $string) call; this seems to be resolved with
-        # Debian Lenny packages sometime between 2009-07-27 and 2010-02-15
-        my $cql_query = decode_utf8($req->query);
-        my $search_string = decode_utf8($req->cql->toEvergreen);
+		# Older versions of Debian packages returned terms to us double-encoded,
+		# so we had to forcefully double-decode them a second time with
+		# an outer decode('utf8', $string) call; this seems to be resolved with
+		# Debian Lenny packages sometime between 2009-07-27 and 2010-02-15
+		my $cql_query = decode_utf8($req->query);
+		my $search_string = decode_utf8($req->cql->toEvergreen);
 
-        # Ensure the search string overrides the default site
-        if ($shortname and $search_string !~ m#site:#) {
-            $search_string .= " site:$shortname";
-        }
+		# Ensure the search string overrides the default site
+		if ($shortname and $search_string !~ m#site:#) {
+			$search_string .= " site:$shortname";
+		}
 
         my $offset = $req->startRecord;
         $offset-- if ($offset);
@@ -1872,85 +1872,85 @@ sub sru_search {
 
         $log->info("SRU search string [$cql_query] converted to [$search_string]\n");
 
-         my $recs = $search->request(
-            'open-ils.search.biblio.multiclass.query' => {offset => $offset, limit => $limit} => $search_string => 1
-        )->gather(1);
-
-        my $bre = $supercat->request( 'open-ils.supercat.record.object.retrieve' => [ map { $_->[0] } @{$recs->{ids}} ] )->gather(1);
-
-        foreach my $record (@$bre) {
-            my $marcxml = $record->marc;
-            # Make the beast conform to a VDX-supported format
-            # See http://vdxipedia.oclc.org/index.php/Holdings_Parsing
-            # Trying to implement LIBSOL_852_A format; so much for standards
-            if ($holdings) {
-                my $bib_holdings = $supercat->request('open-ils.supercat.record.basic_holdings.retrieve', $record->id, $shortname || '-')->gather(1);
-                my $marc = MARC::Record->new_from_xml($marcxml, 'UTF8', 'XML');
-
-                # Force record leader to 'a' as our data is always UTF8
-                # Avoids marc8_to_utf8 from being invoked with horrible results
-                # on the off-chance the record leader isn't correct
-                my $ldr = $marc->leader;
-                substr($ldr, 9, 1, 'a');
-                $marc->leader($ldr);
-
-                # Expects the record ID in the 001
-                $marc->delete_field($_) for ($marc->field('001'));
-                if (!$marc->field('001')) {
-                    $marc->insert_fields_ordered(
-                        MARC::Field->new( '001', $record->id )
-                    );
-                }
-                $marc->delete_field($_) for ($marc->field('852')); # remove any legacy 852s
-                foreach my $cn (keys %$bib_holdings) {
-                    foreach my $cp (@{$bib_holdings->{$cn}->{'copies'}}) {
-                        $marc->insert_fields_ordered(
-                            MARC::Field->new(
-                                '852', '4', '',
-                                a => $cp->{'location'},
-                                b => $bib_holdings->{$cn}->{'owning_lib'},
-                                c => $cn,
-                                d => $cp->{'circlib'},
-                                g => $cp->{'barcode'},
-                                n => $cp->{'status'},
-                            )
-                        );
-                    }
-                }
-
-                # Ensure the data is encoded as UTF8 before we hand it off
-                $marcxml = encode_utf8($marc->as_xml_record());
-                $marcxml =~ s/^<\?xml version="1.0" encoding="UTF-8"\?>//o;
-
-            }
-            $resp->addRecord(
-                SRU::Response::Record->new(
-                    recordSchema    => 'info:srw/schema/1/marcxml-v1.1',
-                    recordData => $marcxml,
-                    recordPosition => ++$offset
-                )
-            );
-        }
-
-        $resp->numberOfRecords($recs->{count});
-
-    } elsif ( $resp->type eq 'explain' ) {
-        return_sru_explain($cgi, $req, $resp, \$ex_doc,
-            \%OpenILS::WWW::SuperCat::nested_qualifier_map,
-            \%OpenILS::WWW::SuperCat::qualifier_ids
-        );
-
-        $resp->record(
-            SRU::Response::Record->new(
-                recordSchema    => 'info:srw/cql-context-set/2/zeerex-1.1',
-                recordData        => $ex_doc
-            )
-        );
-    }
-
-    print $cgi->header( -type => 'application/xml' );
-    print $U->entityize($resp->asXML) . "\n";
-    return Apache2::Const::OK;
+ 		my $recs = $search->request(
+			'open-ils.search.biblio.multiclass.query' => {offset => $offset, limit => $limit} => $search_string => 1
+		)->gather(1);
+
+		my $bre = $supercat->request( 'open-ils.supercat.record.object.retrieve' => [ map { $_->[0] } @{$recs->{ids}} ] )->gather(1);
+
+		foreach my $record (@$bre) {
+			my $marcxml = $record->marc;
+			# Make the beast conform to a VDX-supported format
+			# See http://vdxipedia.oclc.org/index.php/Holdings_Parsing
+			# Trying to implement LIBSOL_852_A format; so much for standards
+			if ($holdings) {
+				my $bib_holdings = $supercat->request('open-ils.supercat.record.basic_holdings.retrieve', $record->id, $shortname || '-')->gather(1);
+				my $marc = MARC::Record->new_from_xml($marcxml, 'UTF8', 'XML');
+
+				# Force record leader to 'a' as our data is always UTF8
+				# Avoids marc8_to_utf8 from being invoked with horrible results
+				# on the off-chance the record leader isn't correct
+				my $ldr = $marc->leader;
+				substr($ldr, 9, 1, 'a');
+				$marc->leader($ldr);
+
+				# Expects the record ID in the 001
+				$marc->delete_field($_) for ($marc->field('001'));
+				if (!$marc->field('001')) {
+					$marc->insert_fields_ordered(
+						MARC::Field->new( '001', $record->id )
+					);
+				}
+				$marc->delete_field($_) for ($marc->field('852')); # remove any legacy 852s
+				foreach my $cn (keys %$bib_holdings) {
+					foreach my $cp (@{$bib_holdings->{$cn}->{'copies'}}) {
+						$marc->insert_fields_ordered(
+							MARC::Field->new(
+								'852', '4', '',
+								a => $cp->{'location'},
+								b => $bib_holdings->{$cn}->{'owning_lib'},
+								c => $cn,
+								d => $cp->{'circlib'},
+								g => $cp->{'barcode'},
+								n => $cp->{'status'},
+							)
+						);
+					}
+				}
+
+				# Ensure the data is encoded as UTF8 before we hand it off
+				$marcxml = encode_utf8($marc->as_xml_record());
+				$marcxml =~ s/^<\?xml version="1.0" encoding="UTF-8"\?>//o;
+
+			}
+			$resp->addRecord(
+				SRU::Response::Record->new(
+					recordSchema    => 'info:srw/schema/1/marcxml-v1.1',
+					recordData => $marcxml,
+					recordPosition => ++$offset
+				)
+			);
+		}
+
+		$resp->numberOfRecords($recs->{count});
+
+	} elsif ( $resp->type eq 'explain' ) {
+		return_sru_explain($cgi, $req, $resp, \$ex_doc,
+			\%OpenILS::WWW::SuperCat::nested_qualifier_map,
+			\%OpenILS::WWW::SuperCat::qualifier_ids
+		);
+
+		$resp->record(
+			SRU::Response::Record->new(
+				recordSchema	=> 'info:srw/cql-context-set/2/zeerex-1.1',
+				recordData		=> $ex_doc
+			)
+		);
+	}
+
+	print $cgi->header( -type => 'application/xml' );
+	print $U->entityize($resp->asXML) . "\n";
+	return Apache2::Const::OK;
 }
 
 
@@ -2189,24 +2189,24 @@ Returns an aou object for a given actor.org_unit shortname or ID.
 =cut
 
 sub get_ou {
-    my $org = shift || '-';
-    my $org_unit;
-
-    if ($org eq '-') {
-         $org_unit = $actor->request(
-            'open-ils.actor.org_unit_list.search' => parent_ou => undef
-        )->gather(1);
-    } elsif ($org !~ /^\d+$/o) {
-         $org_unit = $actor->request(
-            'open-ils.actor.org_unit_list.search' => shortname => uc($org)
-        )->gather(1);
-    } else {
-         $org_unit = $actor->request(
-            'open-ils.actor.org_unit_list.search' => id => $org
-        )->gather(1);
-    }
-
-    return $org_unit;
+	my $org = shift || '-';
+	my $org_unit;
+
+	if ($org eq '-') {
+	 	$org_unit = $actor->request(
+			'open-ils.actor.org_unit_list.search' => parent_ou => undef
+		)->gather(1);
+	} elsif ($org !~ /^\d+$/o) {
+	 	$org_unit = $actor->request(
+			'open-ils.actor.org_unit_list.search' => shortname => uc($org)
+		)->gather(1);
+	} else {
+	 	$org_unit = $actor->request(
+			'open-ils.actor.org_unit_list.search' => id => $org
+		)->gather(1);
+	}
+
+	return $org_unit;
 }
 
 1;

commit 21ac8678de9d2ae0a9c39768ef38dd1dd94e64d7
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Thu Mar 3 00:26:52 2011 -0500

    Revert "Draw SRU search indexes from config.metabib_search_alias"
    
    This reverts commit 5d2354f69e6a71b9151cf71098e02a7574298ff9.

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm
index 658a34d..425d098 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm
@@ -346,7 +346,7 @@ Returns the XML representation of the requested bibliographic record's holdings
 		  params   =>
 		  	[
 				{ name => 'label',
-				  desc => 'The target call number label',
+				  desc => 'The target call number lable',
 				  type => 'string' },
 				{ name => 'org_unit',
 				  desc => 'The org unit shortname (or "-" or undef for global) to browse',
@@ -469,7 +469,7 @@ Returns the XML representation of the requested bibliographic record's holdings
 		  params   =>
 		  	[
 				{ name => 'label',
-				  desc => 'The target call number label',
+				  desc => 'The target call number lable',
 				  type => 'string' },
 				{ name => 'org_unit',
 				  desc => 'The org unit shortname (or "-" or undef for global) to browse',
@@ -2841,41 +2841,6 @@ Returns the ISBN list for the metarecord of the requested isbn
 		}
 );
 
-sub return_bib_search_aliases {
-    my %aliases;
-
-	my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
-
-	my $cmsa = $_storage->request(
-		'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
-		{ alias => { like => '%.%'} }
-	)->gather(1);
-
-    foreach my $alias (@$cmsa) {
-        my ($qualifier, $name) = $alias->alias =~ m/^(.+?)\.(.+)$/;
-        push(@{$aliases{$qualifier}}, $name);
-    }
-
-    return \%aliases;
-}
-
-__PACKAGE__->register_method(
-	method    => 'return_bib_search_aliases',
-	api_name  => 'open-ils.supercat.biblio.search_aliases',
-	api_level => 1,
-	argc      => 0,
-	signature =>
-		{ desc     => <<"		  DESC",
-Returns the set of qualified search aliases in the system
-		  DESC
-		  params   => [ ],
-		  'return' =>
-		  	{ desc => 'Hash of qualified search aliases',
-			  type => 'object' }
-		}
-);
-
-
 package OpenILS::Application::SuperCat::unAPI;
 use base qw/OpenILS::Application::SuperCat/;
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
index 92cab95..f473ee8 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
@@ -34,7 +34,7 @@ my $log = 'OpenSRF::Utils::Logger';
 my $U = 'OpenILS::Application::AppUtils';
 
 # set the bootstrap config when this module is loaded
-my ($bootstrap, $supercat, $actor, $parser, $search, $xslt, $cn_browse_xslt, %browse_types, %qualifier_map);
+my ($bootstrap, $supercat, $actor, $parser, $search, $xslt, $cn_browse_xslt, %browse_types);
 
 $browse_types{call_number}{xml} = sub {
     my $tree = shift;
@@ -143,10 +143,6 @@ sub child_init {
 
     $cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
 
-    %qualifier_map = %{$supercat
-        ->request("open-ils.supercat.biblio.search_aliases")
-        ->gather(1)};
-
     my $list = $supercat
         ->request("open-ils.supercat.record.formats")
         ->gather(1);
@@ -1605,6 +1601,91 @@ sub item_age_browse {
     return Apache2::Const::OK;
 }
 
+our %qualifier_map = (
+
+    # Some EG qualifiers
+    'eg.site'               => 'site',
+    'eg.sort'               => 'sort',
+    'eg.direction'          => 'dir',
+    'eg.available'          => 'available',
+
+    # Title class:
+    'eg.title'              => 'title',
+    'dc.title'              => 'title',
+    'bib.titleabbreviated'  => 'title|abbreviated',
+    'bib.titleuniform'      => 'title|uniform',
+    'bib.titletranslated'   => 'title|translated',
+    'bib.titlealternative'  => 'title',
+    'bib.titleseries'       => 'series',
+    'eg.series'             => 'title',
+
+    # Author/Name class:
+    'eg.author'             => 'author',
+    'eg.name'               => 'author',
+    'creator'               => 'author',
+    'dc.creator'            => 'author',
+    'dc.contributer'        => 'author',
+    'dc.publisher'          => 'keyword',
+    'bib.name'              => 'author',
+    'bib.namepersonal'      => 'author|personal',
+    'bib.namepersonalfamily'=> 'author|personal',
+    'bib.namepersonalgiven' => 'author|personal',
+    'bib.namecorporate'     => 'author|corporate',
+    'bib.nameconference'    => 'author|conference',
+
+    # Subject class:
+    'eg.subject'            => 'subject',
+    'dc.subject'            => 'subject',
+    'bib.subjectplace'      => 'subject|geographic',
+    'bib.subjecttitle'      => 'keyword',
+    'bib.subjectname'       => 'subject|name',
+    'bib.subjectoccupation' => 'keyword',
+
+    # Keyword class:
+    'eg.keyword'            => 'keyword',
+    'srw.serverchoice'      => 'keyword',
+
+    # Identifiers:
+    'dc.identifier'         => 'keyword',
+
+    # Dates:
+    'bib.dateissued'        => undef,
+    'bib.datecreated'       => undef,
+    'bib.datevalid'         => undef,
+    'bib.datemodified'      => undef,
+    'bib.datecopyright'     => undef,
+
+    # Resource Type:
+    'dc.type'               => undef,
+
+    # Format:
+    'dc.format'             => undef,
+
+    # Genre:
+    'bib.genre'             => 'keyword',
+
+    # Target Audience:
+    'bib.audience'          => undef,
+
+    # Place of Origin:
+    'bib.originplace'       => undef,
+
+    # Language
+    'dc.language'           => 'lang',
+
+    # Edition
+    'bib.edition'           => 'keyword',
+
+    # Part:
+    'bib.volume'            => 'keyword',
+    'bib.issue'             => 'keyword',
+    'bib.startpage'         => 'keyword',
+    'bib.endpage'           => 'keyword',
+
+    # Issuance:
+    'bib.issuance'          => 'keyword',
+);
+
 our %qualifier_ids = (
     eg  => 'http://open-ils.org/spec/SRU/context-set/evergreen/v1',
     dc  => 'info:srw/cql-context-set/1/dc-v1.1',
@@ -2020,7 +2101,8 @@ sub return_sru_explain {
 
             $e->findnodes('/z:explain/z:indexInfo')->shift->appendChild( $set_node );
 
-            for my $index ( @{$qualifier_map{$name}} ) {
+            for my $index ( keys %{ $qualifier_map->{$name} } ) {
+                my $desc = $qualifier_map->{$name}{$index}[1] || $index;
 
                 my $name_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'name' );
 
@@ -2033,10 +2115,10 @@ sub return_sru_explain {
                 $index_node->appendChild( $title_node );
                 $index_node->appendChild( $map_node );
 
-                $index_node->setAttribute( id => "$name.$index" );
-                $title_node->appendText( $index);
+                $index_node->setAttribute( id => $name . '.' . $index );
+                $title_node->appendText( $desc );
                 $name_node->setAttribute( set => $name );
-                $name_node->appendText($index);
+                $name_node->appendText($index );
 
                 $e->findnodes('/z:explain/z:indexInfo')->shift->appendChild( $index_node );
             }

commit 5d2354f69e6a71b9151cf71098e02a7574298ff9
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Thu Mar 3 00:11:48 2011 -0500

    Draw SRU search indexes from config.metabib_search_alias
    
    We had hardcoded search indexes for SRU, but thanks to the work
    of Mike Rylander, it is fairly easy to remove that brittle code
    and replace it with code that polls the contents of
    config.metabib_search_alias and dumps it into the explain document.

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm
index 425d098..658a34d 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm
@@ -346,7 +346,7 @@ Returns the XML representation of the requested bibliographic record's holdings
 		  params   =>
 		  	[
 				{ name => 'label',
-				  desc => 'The target call number lable',
+				  desc => 'The target call number label',
 				  type => 'string' },
 				{ name => 'org_unit',
 				  desc => 'The org unit shortname (or "-" or undef for global) to browse',
@@ -469,7 +469,7 @@ Returns the XML representation of the requested bibliographic record's holdings
 		  params   =>
 		  	[
 				{ name => 'label',
-				  desc => 'The target call number lable',
+				  desc => 'The target call number label',
 				  type => 'string' },
 				{ name => 'org_unit',
 				  desc => 'The org unit shortname (or "-" or undef for global) to browse',
@@ -2841,6 +2841,41 @@ Returns the ISBN list for the metarecord of the requested isbn
 		}
 );
 
+sub return_bib_search_aliases {
+    my %aliases;
+
+	my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
+
+	my $cmsa = $_storage->request(
+		'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
+		{ alias => { like => '%.%'} }
+	)->gather(1);
+
+    foreach my $alias (@$cmsa) {
+        my ($qualifier, $name) = $alias->alias =~ m/^(.+?)\.(.+)$/;
+        push(@{$aliases{$qualifier}}, $name);
+    }
+
+    return \%aliases;
+}
+
+__PACKAGE__->register_method(
+	method    => 'return_bib_search_aliases',
+	api_name  => 'open-ils.supercat.biblio.search_aliases',
+	api_level => 1,
+	argc      => 0,
+	signature =>
+		{ desc     => <<"		  DESC",
+Returns the set of qualified search aliases in the system
+		  DESC
+		  params   => [ ],
+		  'return' =>
+		  	{ desc => 'Hash of qualified search aliases',
+			  type => 'object' }
+		}
+);
+
+
 package OpenILS::Application::SuperCat::unAPI;
 use base qw/OpenILS::Application::SuperCat/;
 
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
index f473ee8..92cab95 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
@@ -34,7 +34,7 @@ my $log = 'OpenSRF::Utils::Logger';
 my $U = 'OpenILS::Application::AppUtils';
 
 # set the bootstrap config when this module is loaded
-my ($bootstrap, $supercat, $actor, $parser, $search, $xslt, $cn_browse_xslt, %browse_types);
+my ($bootstrap, $supercat, $actor, $parser, $search, $xslt, $cn_browse_xslt, %browse_types, %qualifier_map);
 
 $browse_types{call_number}{xml} = sub {
     my $tree = shift;
@@ -143,6 +143,10 @@ sub child_init {
 
     $cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
 
+    %qualifier_map = %{$supercat
+        ->request("open-ils.supercat.biblio.search_aliases")
+        ->gather(1)};
+
     my $list = $supercat
         ->request("open-ils.supercat.record.formats")
         ->gather(1);
@@ -1601,91 +1605,6 @@ sub item_age_browse {
     return Apache2::Const::OK;
 }
 
-our %qualifier_map = (
-
-    # Some EG qualifiers
-    'eg.site'               => 'site',
-    'eg.sort'               => 'sort',
-    'eg.direction'          => 'dir',
-    'eg.available'          => 'available',
-
-    # Title class:
-    'eg.title'              => 'title',
-    'dc.title'              => 'title',
-    'bib.titleabbreviated'  => 'title|abbreviated',
-    'bib.titleuniform'      => 'title|uniform',
-    'bib.titletranslated'   => 'title|translated',
-    'bib.titlealternative'  => 'title',
-    'bib.titleseries'       => 'series',
-    'eg.series'             => 'title',
-
-    # Author/Name class:
-    'eg.author'             => 'author',
-    'eg.name'               => 'author',
-    'creator'               => 'author',
-    'dc.creator'            => 'author',
-    'dc.contributer'        => 'author',
-    'dc.publisher'          => 'keyword',
-    'bib.name'              => 'author',
-    'bib.namepersonal'      => 'author|personal',
-    'bib.namepersonalfamily'=> 'author|personal',
-    'bib.namepersonalgiven' => 'author|personal',
-    'bib.namecorporate'     => 'author|corporate',
-    'bib.nameconference'    => 'author|conference',
-
-    # Subject class:
-    'eg.subject'            => 'subject',
-    'dc.subject'            => 'subject',
-    'bib.subjectplace'      => 'subject|geographic',
-    'bib.subjecttitle'      => 'keyword',
-    'bib.subjectname'       => 'subject|name',
-    'bib.subjectoccupation' => 'keyword',
-
-    # Keyword class:
-    'eg.keyword'            => 'keyword',
-    'srw.serverchoice'      => 'keyword',
-
-    # Identifiers:
-    'dc.identifier'         => 'keyword',
-
-    # Dates:
-    'bib.dateissued'        => undef,
-    'bib.datecreated'       => undef,
-    'bib.datevalid'         => undef,
-    'bib.datemodified'      => undef,
-    'bib.datecopyright'     => undef,
-
-    # Resource Type:
-    'dc.type'               => undef,
-
-    # Format:
-    'dc.format'             => undef,
-
-    # Genre:
-    'bib.genre'             => 'keyword',
-
-    # Target Audience:
-    'bib.audience'          => undef,
-
-    # Place of Origin:
-    'bib.originplace'       => undef,
-
-    # Language
-    'dc.language'           => 'lang',
-
-    # Edition
-    'bib.edition'           => 'keyword',
-
-    # Part:
-    'bib.volume'            => 'keyword',
-    'bib.issue'             => 'keyword',
-    'bib.startpage'         => 'keyword',
-    'bib.endpage'           => 'keyword',
-
-    # Issuance:
-    'bib.issuance'          => 'keyword',
-);
-
 our %qualifier_ids = (
     eg  => 'http://open-ils.org/spec/SRU/context-set/evergreen/v1',
     dc  => 'info:srw/cql-context-set/1/dc-v1.1',
@@ -2101,8 +2020,7 @@ sub return_sru_explain {
 
             $e->findnodes('/z:explain/z:indexInfo')->shift->appendChild( $set_node );
 
-            for my $index ( keys %{ $qualifier_map->{$name} } ) {
-                my $desc = $qualifier_map->{$name}{$index}[1] || $index;
+            for my $index ( @{$qualifier_map{$name}} ) {
 
                 my $name_node = $doc->createElementNS( 'http://explain.z3950.org/dtd/2.0/', 'name' );
 
@@ -2115,10 +2033,10 @@ sub return_sru_explain {
                 $index_node->appendChild( $title_node );
                 $index_node->appendChild( $map_node );
 
-                $index_node->setAttribute( id => $name . '.' . $index );
-                $title_node->appendText( $desc );
+                $index_node->setAttribute( id => "$name.$index" );
+                $title_node->appendText( $index);
                 $name_node->setAttribute( set => $name );
-                $name_node->appendText($index );
+                $name_node->appendText($index);
 
                 $e->findnodes('/z:explain/z:indexInfo')->shift->appendChild( $index_node );
             }

commit ea807c1f535a12725f82038bd823d88c0c28a97d
Author: Dan Scott <gitorious at coffeecode.net>
Date:   Wed Mar 2 22:16:39 2011 -0500

    Whitespace consistency for SuperCat.pm

diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
index ae20208..f473ee8 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/SuperCat.pm
@@ -37,115 +37,115 @@ my $U = 'OpenILS::Application::AppUtils';
 my ($bootstrap, $supercat, $actor, $parser, $search, $xslt, $cn_browse_xslt, %browse_types);
 
 $browse_types{call_number}{xml} = sub {
-	my $tree = shift;
+    my $tree = shift;
 
-	my $year = (gmtime())[5] + 1900;
-	my $content = '';
+    my $year = (gmtime())[5] + 1900;
+    my $content = '';
 
-	$content .= "<volumes  xmlns='http://open-ils.org/spec/holdings/v1'>\n";
+    $content .= "<volumes  xmlns='http://open-ils.org/spec/holdings/v1'>\n";
 
-	for my $cn (@$tree) {
-		(my $cn_class = $cn->class_name) =~ s/::/-/gso;
-		$cn_class =~ s/Fieldmapper-//gso;
+    for my $cn (@$tree) {
+        (my $cn_class = $cn->class_name) =~ s/::/-/gso;
+        $cn_class =~ s/Fieldmapper-//gso;
 
-		my $cn_tag = "tag:open-ils.org,$year:$cn_class/".$cn->id;
-		my $cn_lib = $cn->owning_lib->shortname;
-		my $cn_label = $cn->label;
+        my $cn_tag = "tag:open-ils.org,$year:$cn_class/".$cn->id;
+        my $cn_lib = $cn->owning_lib->shortname;
+        my $cn_label = $cn->label;
 
-		$cn_label =~ s/\n//gos;
-		$cn_label =~ s/&/&amp;/go;
-		$cn_label =~ s/'/&apos;/go;
-		$cn_label =~ s/</&lt;/go;
-		$cn_label =~ s/>/&gt;/go;
+        $cn_label =~ s/\n//gos;
+        $cn_label =~ s/&/&amp;/go;
+        $cn_label =~ s/'/&apos;/go;
+        $cn_label =~ s/</&lt;/go;
+        $cn_label =~ s/>/&gt;/go;
 
-		(my $ou_class = $cn->owning_lib->class_name) =~ s/::/-/gso;
-		$ou_class =~ s/Fieldmapper-//gso;
+        (my $ou_class = $cn->owning_lib->class_name) =~ s/::/-/gso;
+        $ou_class =~ s/Fieldmapper-//gso;
 
-		my $ou_tag = "tag:open-ils.org,$year:$ou_class/".$cn->owning_lib->id;
-		my $ou_name = $cn->owning_lib->name;
+        my $ou_tag = "tag:open-ils.org,$year:$ou_class/".$cn->owning_lib->id;
+        my $ou_name = $cn->owning_lib->name;
 
-		$ou_name =~ s/\n//gos;
-		$ou_name =~ s/'/&apos;/go;
+        $ou_name =~ s/\n//gos;
+        $ou_name =~ s/'/&apos;/go;
 
-		(my $rec_class = $cn->record->class_name) =~ s/::/-/gso;
-		$rec_class =~ s/Fieldmapper-//gso;
+        (my $rec_class = $cn->record->class_name) =~ s/::/-/gso;
+        $rec_class =~ s/Fieldmapper-//gso;
 
-		my $rec_tag = "tag:open-ils.org,$year:$rec_class/".$cn->record->id.'/'.$cn->owning_lib->shortname;
+        my $rec_tag = "tag:open-ils.org,$year:$rec_class/".$cn->record->id.'/'.$cn->owning_lib->shortname;
 
-		$content .= "<volume id='$cn_tag' lib='$cn_lib' label='$cn_label'>\n";
-		$content .= "<owning_lib xmlns='http://open-ils.org/spec/actors/v1' id='$ou_tag' name='$ou_name'/>\n";
+        $content .= "<volume id='$cn_tag' lib='$cn_lib' label='$cn_label'>\n";
+        $content .= "<owning_lib xmlns='http://open-ils.org/spec/actors/v1' id='$ou_tag' name='$ou_name'/>\n";
 
-		my $r_doc = $parser->parse_string($cn->record->marc);
-		$r_doc->documentElement->setAttribute( id => $rec_tag );
-		$content .= $U->entityize($r_doc->documentElement->toString);
+        my $r_doc = $parser->parse_string($cn->record->marc);
+        $r_doc->documentElement->setAttribute( id => $rec_tag );
+        $content .= $U->entityize($r_doc->documentElement->toString);
 
-		$content .= "</volume>\n";
-	}
+        $content .= "</volume>\n";
+    }
 
-	$content .= "</volumes>\n";
-	return ("Content-type: application/xml\n\n",$content);
+    $content .= "</volumes>\n";
+    return ("Content-type: application/xml\n\n",$content);
 };
 
 
 $browse_types{call_number}{html} = sub {
-	my $tree = shift;
-	my $p = shift;
-	my $n = shift;
-
-	if (!$cn_browse_xslt) {
-	        $cn_browse_xslt = $parser->parse_file(
-        	        OpenSRF::Utils::SettingsClient
-                	        ->new
-                        	->config_value( dirs => 'xsl' ).
-	                "/CNBrowse2HTML.xsl"
-        	);
-		$cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
-	}
-
-	my (undef,$xml) = $browse_types{call_number}{xml}->($tree);
-
-	return (
-		"Content-type: text/html\n\n",
-		$U->entityize(
-			$cn_browse_xslt->transform(
-				$parser->parse_string( $xml ),
-				'prev' => "'$p'",
-				'next' => "'$n'"
-			)->toString(1)
-		)
-	);
-};
-
-sub import {
-	my $self = shift;
-	$bootstrap = shift;
-}
-
-
-sub child_init {
-	OpenSRF::System->bootstrap_client( config_file => $bootstrap );
-	
-	my $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
-	Fieldmapper->import(IDL => $idl);
-
-	$supercat = OpenSRF::AppSession->create('open-ils.supercat');
-	$actor = OpenSRF::AppSession->create('open-ils.actor');
-	$search = OpenSRF::AppSession->create('open-ils.search');
-	$parser = new XML::LibXML;
-	$xslt = new XML::LibXSLT;
+    my $tree = shift;
+    my $p = shift;
+    my $n = shift;
 
+    if (!$cn_browse_xslt) {
         $cn_browse_xslt = $parser->parse_file(
                 OpenSRF::Utils::SettingsClient
                         ->new
                         ->config_value( dirs => 'xsl' ).
                 "/CNBrowse2HTML.xsl"
         );
+        $cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
+    }
+
+    my (undef,$xml) = $browse_types{call_number}{xml}->($tree);
+
+    return (
+        "Content-type: text/html\n\n",
+        $U->entityize(
+            $cn_browse_xslt->transform(
+                $parser->parse_string( $xml ),
+                'prev' => "'$p'",
+                'next' => "'$n'"
+            )->toString(1)
+        )
+    );
+};
+
+sub import {
+    my $self = shift;
+    $bootstrap = shift;
+}
+
+
+sub child_init {
+    OpenSRF::System->bootstrap_client( config_file => $bootstrap );
+    
+    my $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
+    Fieldmapper->import(IDL => $idl);
+
+    $supercat = OpenSRF::AppSession->create('open-ils.supercat');
+    $actor = OpenSRF::AppSession->create('open-ils.actor');
+    $search = OpenSRF::AppSession->create('open-ils.search');
+    $parser = new XML::LibXML;
+    $xslt = new XML::LibXSLT;
+
+    $cn_browse_xslt = $parser->parse_file(
+            OpenSRF::Utils::SettingsClient
+                    ->new
+                    ->config_value( dirs => 'xsl' ).
+            "/CNBrowse2HTML.xsl"
+    );
 
-	$cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
+    $cn_browse_xslt = $xslt->parse_stylesheet( $cn_browse_xslt );
 
-	my $list = $supercat
-		->request("open-ils.supercat.record.formats")
-		->gather(1);
+    my $list = $supercat
+        ->request("open-ils.supercat.record.formats")
+        ->gather(1);
 
     $list = [ map { (keys %$_)[0] } @$list ];
     push @$list, 'htmlholdings','html', 'marctxt', 'ris';
@@ -157,22 +157,22 @@ sub child_init {
                 my $__a = $browse_axis;
 
                 $browse_types{$__a}{$__f} = sub {
-                	my $record_list = shift;
-                	my $prev = shift;
-                	my $next = shift;
-                	my $real_format = shift || $__f;
-                	my $unapi = shift;
-                	my $base = shift;
-                	my $site = shift;
-
-					$log->info("Creating record feed with params [$real_format, $record_list, $unapi, $site]");
-                	my $feed = create_record_feed( 'record', $real_format, $record_list, $unapi, $site, undef, $real_format =~ /(-full|-uris)$/o ? 1 : 0 );
-                	$feed->root( "$base/../" );
-                	$feed->lib( $site );
-                	$feed->link( next => $next => $feed->type );
-                	$feed->link( previous => $prev => $feed->type );
-
-                	return (
+                    my $record_list = shift;
+                    my $prev = shift;
+                    my $next = shift;
+                    my $real_format = shift || $__f;
+                    my $unapi = shift;
+                    my $base = shift;
+                    my $site = shift;
+
+                    $log->info("Creating record feed with params [$real_format, $record_list, $unapi, $site]");
+                    my $feed = create_record_feed( 'record', $real_format, $record_list, $unapi, $site, undef, $real_format =~ /(-full|-uris)$/o ? 1 : 0 );
+                    $feed->root( "$base/../" );
+                    $feed->lib( $site );
+                    $feed->link( next => $next => $feed->type );
+                    $feed->link( previous => $prev => $feed->type );
+
+                    return (
                         "Content-type: ". $feed->type ."; charset=utf-8\n\n",
                         $feed->toString
                     );
@@ -188,21 +188,21 @@ sub child_init {
                 my $__a = $browse_axis;
 
                 $browse_types{$__a}{$__f} = sub {
-                	my $record_list = shift;
-                	my $prev = shift;
-                	my $next = shift;
-                	my $real_format = shift || $__f;
-                	my $unapi = shift;
-                	my $base = shift;
-                	my $site = shift;
-
-					$log->info("Creating record feed with params [$real_format, $record_list, $unapi, $site]");
-                	my $feed = create_record_feed( 'authority', $real_format, $record_list, $unapi, $site, undef, $real_format =~ /-full$/o ? -1 : 0 );
-                	$feed->root( "$base/../" );
-                	$feed->link( next => $next => $feed->type );
-                	$feed->link( previous => $prev => $feed->type );
-
-                	return (
+                    my $record_list = shift;
+                    my $prev = shift;
+                    my $next = shift;
+                    my $real_format = shift || $__f;
+                    my $unapi = shift;
+                    my $base = shift;
+                    my $site = shift;
+
+                    $log->info("Creating record feed with params [$real_format, $record_list, $unapi, $site]");
+                    my $feed = create_record_feed( 'authority', $real_format, $record_list, $unapi, $site, undef, $real_format =~ /-full$/o ? -1 : 0 );
+                    $feed->root( "$base/../" );
+                    $feed->link( next => $next => $feed->type );
+                    $feed->link( previous => $prev => $feed->type );
+
+                    return (
                         "Content-type: ". $feed->type ."; charset=utf-8\n\n",
                         $feed->toString
                     );
@@ -228,18 +228,18 @@ Otherwise, we won't return any holdings.
 =cut
 
 sub parse_feed_type {
-	my $type = shift;
+    my $type = shift;
 
- 	if ($type =~ /-full$/o) {
-		return 1;
-	}
+     if ($type =~ /-full$/o) {
+        return 1;
+    }
 
- 	if ($type =~ /-uris$/o) {
-		return "uris";
-	}
+     if ($type =~ /-uris$/o) {
+        return "uris";
+    }
 
-	# Otherwise, we'll return just the facts, ma'am
-	return 0;
+    # Otherwise, we'll return just the facts, ma'am
+    return 0;
 }
 
 =head2 supercat_format($format_hashref, $format_type)
@@ -254,21 +254,21 @@ have to populate the hash with redundant information.
 =cut
 
 sub supercat_format {
-	my $h = shift;
-	my $type = shift;
+    my $h = shift;
+    my $type = shift;
 
-	(my $base_type = $type) =~ s/(-full|-uris)$//o;
+    (my $base_type = $type) =~ s/(-full|-uris)$//o;
 
-	my $format = "<format><name>$type</name><type>application/xml</type>";
+    my $format = "<format><name>$type</name><type>application/xml</type>";
 
-	for my $part ( qw/namespace_uri docs schema_location/ ) {
-		$format .= "<$part>$$h{$base_type}{$part}</$part>"
-			if ($$h{$base_type}{$part});
-	}
+    for my $part ( qw/namespace_uri docs schema_location/ ) {
+        $format .= "<$part>$$h{$base_type}{$part}</$part>"
+            if ($$h{$base_type}{$part});
+    }
 
-	$format .= '</format>';
+    $format .= '</format>';
 
-	return $format;
+    return $format;
 }
 
 =head2 unapi_format($format_hashref, $format_type)
@@ -283,199 +283,199 @@ have to populate the hash with redundant information.
 =cut
 
 sub unapi_format {
-	my $h = shift;
-	my $type = shift;
+    my $h = shift;
+    my $type = shift;
 
-	(my $base_type = $type) =~ s/(-full|-uris)$//o;
+    (my $base_type = $type) =~ s/(-full|-uris)$//o;
 
-	my $format = "<format name='$type' type='application/xml'";
+    my $format = "<format name='$type' type='application/xml'";
 
-	for my $part ( qw/namespace_uri docs schema_location/ ) {
-		$format .= " $part='$$h{$base_type}{$part}'"
-			if ($$h{$base_type}{$part});
-	}
+    for my $part ( qw/namespace_uri docs schema_location/ ) {
+        $format .= " $part='$$h{$base_type}{$part}'"
+            if ($$h{$base_type}{$part});
+    }
 
-	$format .= "/>\n";
+    $format .= "/>\n";
 
-	return $format;
+    return $format;
 }
 
 
 sub oisbn {
 
-	my $apache = shift;
-	return Apache2::Const::DECLINED if (-e $apache->filename);
+    my $apache = shift;
+    return Apache2::Const::DECLINED if (-e $apache->filename);
 
-	(my $isbn = $apache->path_info) =~ s{^.*?([^/]+)$}{$1}o;
+    (my $isbn = $apache->path_info) =~ s{^.*?([^/]+)$}{$1}o;
 
-	my $list = $supercat
-		->request("open-ils.supercat.oisbn", $isbn)
-		->gather(1);
+    my $list = $supercat
+        ->request("open-ils.supercat.oisbn", $isbn)
+        ->gather(1);
 
-	print "Content-type: application/xml; charset=utf-8\n\n";
-	print "<?xml version='1.0' encoding='UTF-8' ?>\n";
+    print "Content-type: application/xml; charset=utf-8\n\n";
+    print "<?xml version='1.0' encoding='UTF-8' ?>\n";
 
-	unless (exists $$list{metarecord}) {
-		print '<idlist/>';
-		return Apache2::Const::OK;
-	}
+    unless (exists $$list{metarecord}) {
+        print '<idlist/>';
+        return Apache2::Const::OK;
+    }
 
-	print "<idlist metarecord='$$list{metarecord}'>\n";
+    print "<idlist metarecord='$$list{metarecord}'>\n";
 
-	for ( keys %{ $$list{record_list} } ) {
-		(my $o = $$list{record_list}{$_}) =~s/^(\S+).*?$/$1/o;
-		print "  <isbn record='$_'>$o</isbn>\n"
-	}
+    for ( keys %{ $$list{record_list} } ) {
+        (my $o = $$list{record_list}{$_}) =~s/^(\S+).*?$/$1/o;
+        print "  <isbn record='$_'>$o</isbn>\n"
+    }
 
-	print "</idlist>\n";
+    print "</idlist>\n";
 
-	return Apache2::Const::OK;
+    return Apache2::Const::OK;
 }
 
 sub unapi {
 
-	my $apache = shift;
-	return Apache2::Const::DECLINED if (-e $apache->filename);
+    my $apache = shift;
+    return Apache2::Const::DECLINED if (-e $apache->filename);
 
-	my $cgi = new CGI;
+    my $cgi = new CGI;
 
-	my $add_path = 0;
-	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-		my $rel_name = $cgi->url(-relative=>1);
-		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-	}
+    my $add_path = 0;
+    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+        my $rel_name = $cgi->url(-relative=>1);
+        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+    }
 
-	my $url = $cgi->url(-path_info=>$add_path);
-	my $root = (split 'unapi', $url)[0];
-	my $base = (split 'unapi', $url)[0] . 'unapi';
+    my $url = $cgi->url(-path_info=>$add_path);
+    my $root = (split 'unapi', $url)[0];
+    my $base = (split 'unapi', $url)[0] . 'unapi';
 
 
-	my $uri = $cgi->param('id') || '';
-	my $host = $cgi->virtual_host || $cgi->server_name;
+    my $uri = $cgi->param('id') || '';
+    my $host = $cgi->virtual_host || $cgi->server_name;
 
-	my $skin = $cgi->param('skin') || 'default';
-	my $locale = $cgi->param('locale') || 'en-US';
+    my $skin = $cgi->param('skin') || 'default';
+    my $locale = $cgi->param('locale') || 'en-US';
+
+    # Enable localized results of copy status, etc
+    $supercat->session_locale($locale);
+
+    my $format = $cgi->param('format');
+    my $flesh_feed = parse_feed_type($format);
+    (my $base_format = $format) =~ s/(-full|-uris)$//o;
+    my ($id,$type,$command,$lib,$depth,$paging) = ('','','');
+
+    if (!$format) {
+        my $body = "Content-type: application/xml; charset=utf-8\n\n";
+    
+        if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^\/[]+)(?:\[([0-9,]+)\])?(?:/(.+))?}o) {
+            $id = $2;
+            $paging = $3;
+            ($lib,$depth) = split('/', $4);
+            $type = 'record';
+            $type = 'metarecord' if ($1 =~ /^m/o);
+            $type = 'authority' if ($1 =~ /^authority/o);
+
+            my $list = $supercat
+                ->request("open-ils.supercat.$type.formats")
+                ->gather(1);
+
+            if ($type eq 'record' or $type eq 'isbn') {
+                $body .= <<"                FORMATS";
+<formats id='$uri'>
+    <format name='opac' type='text/html'/>
+    <format name='html' type='text/html'/>
+    <format name='htmlholdings' type='text/html'/>
+    <format name='holdings_xml' type='application/xml'/>
+    <format name='holdings_xml-full' type='application/xml'/>
+    <format name='html-full' type='text/html'/>
+    <format name='htmlholdings-full' type='text/html'/>
+    <format name='marctxt' type='text/plain'/>
+    <format name='ris' type='text/plain'/>
+                FORMATS
+            } elsif ($type eq 'metarecord') {
+                $body .= <<"                FORMATS";
+                <formats id='$uri'>
+                    <format name='opac' type='text/html'/>
+                FORMATS
+            } else {
+                $body .= <<"                FORMATS";
+                <formats id='$uri'>
+                FORMATS
+            }
 
-	# Enable localized results of copy status, etc
-	$supercat->session_locale($locale);
+            for my $h (@$list) {
+                my ($type) = keys %$h;
+                $body .= unapi_format($h, $type);
 
-	my $format = $cgi->param('format');
-	my $flesh_feed = parse_feed_type($format);
-	(my $base_format = $format) =~ s/(-full|-uris)$//o;
-	my ($id,$type,$command,$lib,$depth,$paging) = ('','','');
+                if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
+                    $body .= unapi_format($h, "$type-full");
+                    $body .= unapi_format($h, "$type-uris");
+                }
+            }
 
-	if (!$format) {
-		my $body = "Content-type: application/xml; charset=utf-8\n\n";
-	
-		if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^\/[]+)(?:\[([0-9,]+)\])?(?:/(.+))?}o) {
-			$id = $2;
-			$paging = $3;
-			($lib,$depth) = split('/', $4);
-			$type = 'record';
-			$type = 'metarecord' if ($1 =~ /^m/o);
-			$type = 'authority' if ($1 =~ /^authority/o);
+            $body .= "</formats>\n";
 
-			my $list = $supercat
-				->request("open-ils.supercat.$type.formats")
-				->gather(1);
+        } else {
+            my $list = $supercat
+                ->request("open-ils.supercat.$type.formats")
+                ->gather(1);
+                
+            push @$list,
+                @{ $supercat
+                    ->request("open-ils.supercat.metarecord.formats")
+                    ->gather(1);
+                };
 
-			if ($type eq 'record' or $type eq 'isbn') {
-				$body .= <<"				FORMATS";
-<formats id='$uri'>
-	<format name='opac' type='text/html'/>
-	<format name='html' type='text/html'/>
-	<format name='htmlholdings' type='text/html'/>
-	<format name='holdings_xml' type='application/xml'/>
-	<format name='holdings_xml-full' type='application/xml'/>
-	<format name='html-full' type='text/html'/>
-	<format name='htmlholdings-full' type='text/html'/>
-	<format name='marctxt' type='text/plain'/>
-	<format name='ris' type='text/plain'/>
-				FORMATS
-			} elsif ($type eq 'metarecord') {
-				$body .= <<"				FORMATS";
-				<formats id='$uri'>
-					<format name='opac' type='text/html'/>
-				FORMATS
-			} else {
-				$body .= <<"				FORMATS";
-				<formats id='$uri'>
-				FORMATS
-			}
-
-			for my $h (@$list) {
-				my ($type) = keys %$h;
-				$body .= unapi_format($h, $type);
-
-				if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
-					$body .= unapi_format($h, "$type-full");
-					$body .= unapi_format($h, "$type-uris");
-				}
-			}
-
-			$body .= "</formats>\n";
-
-		} else {
-			my $list = $supercat
-				->request("open-ils.supercat.$type.formats")
-				->gather(1);
-				
-			push @$list,
-				@{ $supercat
-					->request("open-ils.supercat.metarecord.formats")
-					->gather(1);
-				};
-
-			my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
-			$list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
-
-			$body .= <<"			FORMATS";
+            my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
+            $list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
+
+            $body .= <<"            FORMATS";
 <formats>
-	<format name='opac' type='text/html'/>
-	<format name='html' type='text/html'/>
-	<format name='htmlholdings' type='text/html'/>
-	<format name='holdings_xml' type='application/xml'/>
-	<format name='holdings_xml-full' type='application/xml'/>
-	<format name='html-full' type='text/html'/>
-	<format name='htmlholdings-full' type='text/html'/>
-	<format name='marctxt' type='text/plain'/>
-	<format name='ris' type='text/plain'/>
-			FORMATS
-
-
-			for my $h (@$list) {
-				my ($type) = keys %$h;
-				$body .= "\t" . unapi_format($h, $type);
-
-				if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
-					$body .= "\t" . unapi_format($h, "$type-full");
-					$body .= "\t" . unapi_format($h, "$type-uris");
-				}
-			}
-
-			$body .= "</formats>\n";
-
-		}
-		print $body;
-		return Apache2::Const::OK;
-	}
-
-	my $scheme;
-	if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^\/[]+)(?:\[([0-9,]+)\])?(?:/(.+))?}o) {
-		$scheme = $1;
-		$id = $2;
-		$paging = $3;
-		($lib,$depth) = split('/', $4);
-		$type = 'record';
-		$type = 'metarecord' if ($scheme =~ /^metabib/o);
-		$type = 'isbn' if ($scheme =~ /^isbn/o);
-		$type = 'acp' if ($scheme =~ /^asset-copy/o);
-		$type = 'acn' if ($scheme =~ /^asset-call_number/o);
-		$type = 'auri' if ($scheme =~ /^asset-uri/o);
-		$type = 'authority' if ($scheme =~ /^authority/o);
-		$command = 'retrieve';
-		$command = 'browse' if (grep { $scheme eq $_ } qw/call_number title author subject topic authority.title authority.author authority.subject authority.topic series item-age/);
-	}
+    <format name='opac' type='text/html'/>
+    <format name='html' type='text/html'/>
+    <format name='htmlholdings' type='text/html'/>
+    <format name='holdings_xml' type='application/xml'/>
+    <format name='holdings_xml-full' type='application/xml'/>
+    <format name='html-full' type='text/html'/>
+    <format name='htmlholdings-full' type='text/html'/>
+    <format name='marctxt' type='text/plain'/>
+    <format name='ris' type='text/plain'/>
+            FORMATS
+
+
+            for my $h (@$list) {
+                my ($type) = keys %$h;
+                $body .= "\t" . unapi_format($h, $type);
+
+                if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
+                    $body .= "\t" . unapi_format($h, "$type-full");
+                    $body .= "\t" . unapi_format($h, "$type-uris");
+                }
+            }
+
+            $body .= "</formats>\n";
+
+        }
+        print $body;
+        return Apache2::Const::OK;
+    }
+
+    my $scheme;
+    if ($uri =~ m{^tag:[^:]+:([^\/]+)/([^\/[]+)(?:\[([0-9,]+)\])?(?:/(.+))?}o) {
+        $scheme = $1;
+        $id = $2;
+        $paging = $3;
+        ($lib,$depth) = split('/', $4);
+        $type = 'record';
+        $type = 'metarecord' if ($scheme =~ /^metabib/o);
+        $type = 'isbn' if ($scheme =~ /^isbn/o);
+        $type = 'acp' if ($scheme =~ /^asset-copy/o);
+        $type = 'acn' if ($scheme =~ /^asset-call_number/o);
+        $type = 'auri' if ($scheme =~ /^asset-uri/o);
+        $type = 'authority' if ($scheme =~ /^authority/o);
+        $command = 'retrieve';
+        $command = 'browse' if (grep { $scheme eq $_ } qw/call_number title author subject topic authority.title authority.author authority.subject authority.topic series item-age/);
+    }
 
     if ($paging) {
         $paging = [split ',', $paging];
@@ -483,563 +483,563 @@ sub unapi {
         $paging = [];
     }
 
-	if (!$lib || $lib eq '-') {
-	 	$lib = $actor->request(
-			'open-ils.actor.org_unit_list.search' => parent_ou => undef
-		)->gather(1)->[0]->shortname;
-	}
-
-	my ($lib_object,$lib_id,$ou_types,$lib_depth);
-	if ($type ne 'acn' && $type ne 'acp' && $type ne 'auri') {
-		$lib_object = $actor->request(
-			'open-ils.actor.org_unit_list.search' => shortname => $lib
-		)->gather(1)->[0];
-		$lib_id = $lib_object->id;
-
-		$ou_types = $actor->request( 'open-ils.actor.org_types.retrieve' )->gather(1);
-		$lib_depth = $depth || (grep { $_->id == $lib_object->ou_type } @$ou_types)[0]->depth;
-	}
-
-	if ($command eq 'browse') {
-		print "Location: $root/browse/$base_format/$scheme/$lib/$id\n\n";
-		return 302;
-	}
-
-	if ($type eq 'isbn') {
-		my $rec = $supercat->request('open-ils.supercat.isbn.object.retrieve',$id)->gather(1);
-		if (!@$rec) {
-			print "Content-type: text/html; charset=utf-8\n\n";
-			$apache->custom_response( 404, <<"			HTML");
-			<html>
-				<head>
-					<title>Type [$type] with id [$id] not found!</title>
-				</head>
-				<body>
-					<br/>
-					<center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
-				</body>
-			</html>
-			HTML
-			return 404;
-		}
-		$id = $rec->[0]->id;
-		$type = 'record';
-	}
-
-	if ( !grep
-		   { (keys(%$_))[0] eq $base_format }
-		   @{ $supercat->request("open-ils.supercat.$type.formats")->gather(1) }
-		 and !grep
-		   { $_ eq $base_format }
-		   qw/opac html htmlholdings marctxt ris holdings_xml/
-	) {
-		print "Content-type: text/html; charset=utf-8\n\n";
-		$apache->custom_response( 406, <<"		HTML");
-		<html>
-			<head>
-				<title>Invalid format [$format] for type [$type]!</title>
-			</head>
-			<body>
-				<br/>
-				<center>Sorry, format $format is not valid for type $type.</center>
-			</body>
-		</html>
-		HTML
-		return 406;
-	}
-
-	if ($format eq 'opac') {
-		print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id&l=$lib_id&d=$lib_depth\n\n"
-			if ($type eq 'metarecord');
-		print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id&l=$lib_id&d=$lib_depth\n\n"
-			if ($type eq 'record');
-		return 302;
-	} elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format) && ($type ne 'acn' && $type ne 'acp' && $type ne 'auri')) {
-		my $feed = create_record_feed(
-			$type,
-			$format => [ $id ],
-			$base,
-			$lib,
-			$depth,
-			$flesh_feed,
+    if (!$lib || $lib eq '-') {
+         $lib = $actor->request(
+            'open-ils.actor.org_unit_list.search' => parent_ou => undef
+        )->gather(1)->[0]->shortname;
+    }
+
+    my ($lib_object,$lib_id,$ou_types,$lib_depth);
+    if ($type ne 'acn' && $type ne 'acp' && $type ne 'auri') {
+        $lib_object = $actor->request(
+            'open-ils.actor.org_unit_list.search' => shortname => $lib
+        )->gather(1)->[0];
+        $lib_id = $lib_object->id;
+
+        $ou_types = $actor->request( 'open-ils.actor.org_types.retrieve' )->gather(1);
+        $lib_depth = $depth || (grep { $_->id == $lib_object->ou_type } @$ou_types)[0]->depth;
+    }
+
+    if ($command eq 'browse') {
+        print "Location: $root/browse/$base_format/$scheme/$lib/$id\n\n";
+        return 302;
+    }
+
+    if ($type eq 'isbn') {
+        my $rec = $supercat->request('open-ils.supercat.isbn.object.retrieve',$id)->gather(1);
+        if (!@$rec) {
+            print "Content-type: text/html; charset=utf-8\n\n";
+            $apache->custom_response( 404, <<"            HTML");
+            <html>
+                <head>
+                    <title>Type [$type] with id [$id] not found!</title>
+                </head>
+                <body>
+                    <br/>
+                    <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
+                </body>
+            </html>
+            HTML
+            return 404;
+        }
+        $id = $rec->[0]->id;
+        $type = 'record';
+    }
+
+    if ( !grep
+           { (keys(%$_))[0] eq $base_format }
+           @{ $supercat->request("open-ils.supercat.$type.formats")->gather(1) }
+         and !grep
+           { $_ eq $base_format }
+           qw/opac html htmlholdings marctxt ris holdings_xml/
+    ) {
+        print "Content-type: text/html; charset=utf-8\n\n";
+        $apache->custom_response( 406, <<"        HTML");
+        <html>
+            <head>
+                <title>Invalid format [$format] for type [$type]!</title>
+            </head>
+            <body>
+                <br/>
+                <center>Sorry, format $format is not valid for type $type.</center>
+            </body>
+        </html>
+        HTML
+        return 406;
+    }
+
+    if ($format eq 'opac') {
+        print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id&l=$lib_id&d=$lib_depth\n\n"
+            if ($type eq 'metarecord');
+        print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id&l=$lib_id&d=$lib_depth\n\n"
+            if ($type eq 'record');
+        return 302;
+    } elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format) && ($type ne 'acn' && $type ne 'acp' && $type ne 'auri')) {
+        my $feed = create_record_feed(
+            $type,
+            $format => [ $id ],
+            $base,
+            $lib,
+            $depth,
+            $flesh_feed,
             $paging
-		);
-
-		if (!$feed->count) {
-			print "Content-type: text/html; charset=utf-8\n\n";
-			$apache->custom_response( 404, <<"			HTML");
-			<html>
-				<head>
-					<title>Type [$type] with id [$id] not found!</title>
-				</head>
-				<body>
-					<br/>
-					<center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
-				</body>
-			</html>
-			HTML
-			return 404;
-		}
-
-		$feed->root($root);
-		$feed->creator($host);
-		$feed->update_ts();
-		$feed->link( unapi => $base) if ($flesh_feed);
-
-		print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
-		print $U->entityize($feed->toString) . "\n";
-
-		return Apache2::Const::OK;
-	}
-
-	my $method = "open-ils.supercat.$type.$base_format.$command";
-	my @params = ($id);
-	push @params, $lib, $lib_depth, $flesh_feed, $paging if ($base_format eq 'holdings_xml');
-
-	# for acn, acp, etc, the "lib" pathinfo position isn't useful.
-	# however, we can have it carry extra options like no_record! (comma separated)
-	push @params, { map { ( $_ => 1 ) } split(',', $lib) } if ( grep { $type eq $_} qw/acn acp auri/);
-
-	my $req = $supercat->request($method, at params);
-	my $data = $req->gather();
-
-	if ($req->failed || !$data) {
-		print "Content-type: text/html; charset=utf-8\n\n";
-		$apache->custom_response( 404, <<"		HTML");
-		<html>
-			<head>
-				<title>$type $id not found!</title>
-			</head>
-			<body>
-				<br/>
-				<center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
-			</body>
-		</html>
-		HTML
-		return 404;
-	}
-
-	print "Content-type: application/xml; charset=utf-8\n\n$data";
-
-	if ($base_format eq 'holdings_xml') {
-		while (my $c = $req->recv) {
-			print $c->content;
-		}
-	}
-
-	return Apache2::Const::OK;
+        );
+
+        if (!$feed->count) {
+            print "Content-type: text/html; charset=utf-8\n\n";
+            $apache->custom_response( 404, <<"            HTML");
+            <html>
+                <head>
+                    <title>Type [$type] with id [$id] not found!</title>
+                </head>
+                <body>
+                    <br/>
+                    <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
+                </body>
+            </html>
+            HTML
+            return 404;
+        }
+
+        $feed->root($root);
+        $feed->creator($host);
+        $feed->update_ts();
+        $feed->link( unapi => $base) if ($flesh_feed);
+
+        print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
+        print $U->entityize($feed->toString) . "\n";
+
+        return Apache2::Const::OK;
+    }
+
+    my $method = "open-ils.supercat.$type.$base_format.$command";
+    my @params = ($id);
+    push @params, $lib, $lib_depth, $flesh_feed, $paging if ($base_format eq 'holdings_xml');
+
+    # for acn, acp, etc, the "lib" pathinfo position isn't useful.
+    # however, we can have it carry extra options like no_record! (comma separated)
+    push @params, { map { ( $_ => 1 ) } split(',', $lib) } if ( grep { $type eq $_} qw/acn acp auri/);
+
+    my $req = $supercat->request($method, at params);
+    my $data = $req->gather();
+
+    if ($req->failed || !$data) {
+        print "Content-type: text/html; charset=utf-8\n\n";
+        $apache->custom_response( 404, <<"        HTML");
+        <html>
+            <head>
+                <title>$type $id not found!</title>
+            </head>
+            <body>
+                <br/>
+                <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
+            </body>
+        </html>
+        HTML
+        return 404;
+    }
+
+    print "Content-type: application/xml; charset=utf-8\n\n$data";
+
+    if ($base_format eq 'holdings_xml') {
+        while (my $c = $req->recv) {
+            print $c->content;
+        }
+    }
+
+    return Apache2::Const::OK;
 }
 
 sub supercat {
 
-	my $apache = shift;
-	return Apache2::Const::DECLINED if (-e $apache->filename);
-
-	my $cgi = new CGI;
-
-	my $add_path = 0;
-	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-		my $rel_name = $cgi->url(-relative=>1);
-		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-	}
-
-	my $url = $cgi->url(-path_info=>$add_path);
-	my $root = (split 'supercat', $url)[0];
-	my $base = (split 'supercat', $url)[0] . 'supercat';
-	my $unapi = (split 'supercat', $url)[0] . 'unapi';
-
-	my $host = $cgi->virtual_host || $cgi->server_name;
-
-	my $path = $cgi->path_info;
-	my ($id,$type,$format,$command) = reverse split '/', $path;
-	my $flesh_feed = parse_feed_type($format);
-	(my $base_format = $format) =~ s/(-full|-uris)$//o;
-
-	my $skin = $cgi->param('skin') || 'default';
-	my $locale = $cgi->param('locale') || 'en-US';
-
-	# Enable localized results of copy status, etc
-	$supercat->session_locale($locale);
-	
-	if ( $path =~ m{^/formats(?:/([^\/]+))?$}o ) {
-		print "Content-type: application/xml; charset=utf-8\n";
-		if ($1) {
-			my $list = $supercat
-				->request("open-ils.supercat.$1.formats")
-				->gather(1);
-
-			print "\n";
-
-			print "<formats>
-				   <format>
-				     <name>opac</name>
-				     <type>text/html</type>
-				   </format>";
-
-			if ($1 eq 'record' or $1 eq 'isbn') {
-				print "<format>
-				     <name>htmlholdings</name>
-				     <type>text/html</type>
-				   </format>
-				   <format>
-				     <name>html</name>
-				     <type>text/html</type>
-				   </format>
-				   <format>
-				     <name>htmlholdings-full</name>
-				     <type>text/html</type>
-				   </format>
-				   <format>
-				     <name>html-full</name>
-				     <type>text/html</type>
-				   </format>
-				   <format>
-				     <name>marctxt</name>
-				     <type>text/plain</type>
-				   </format>
-				   <format>
-				     <name>ris</name>
-				     <type>text/plain</type>
-				   </format>";
-			}
-
-			for my $h (@$list) {
-				my ($type) = keys %$h;
-				print supercat_format($h, $type);
-
-				if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
-					print supercat_format($h, "$type-full");
-					print supercat_format($h, "$type-uris");
-				}
-
-			}
-
-			print "</formats>\n";
-
-			return Apache2::Const::OK;
-		}
-
-		my $list = $supercat
-			->request("open-ils.supercat.record.formats")
-			->gather(1);
-				
-		push @$list,
-			@{ $supercat
-				->request("open-ils.supercat.metarecord.formats")
-				->gather(1);
-			};
-
-		my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
-		$list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
-
-		print "\n<formats>
-			   <format>
-			     <name>opac</name>
-			     <type>text/html</type>
-			   </format>
-			   <format>
-			     <name>htmlholdings</name>
-			     <type>text/html</type>
-			   </format>
-			   <format>
-			     <name>html</name>
-			     <type>text/html</type>
-			   </format>
-			   <format>
-			     <name>htmlholdings-full</name>
-			     <type>text/html</type>
-			   </format>
-			   <format>
-			     <name>html-full</name>
-			     <type>text/html</type>
-			   </format>
-			   <format>
-			     <name>marctxt</name>
-			     <type>text/plain</type>
-			   </format>
-			   <format>
-			     <name>ris</name>
-			     <type>text/plain</type>
-			   </format>";
-
-		for my $h (@$list) {
-			my ($type) = keys %$h;
-			print supercat_format($h, $type);
-
-			if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
-				print supercat_format($h, "$type-full");
-				print supercat_format($h, "$type-uris");
-			}
-
-		}
-
-		print "</formats>\n";
-
-
-		return Apache2::Const::OK;
-	}
-
-	if ($format eq 'opac') {
-		print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id\n\n"
-			if ($type eq 'metarecord');
-		print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id\n\n"
-			if ($type eq 'record');
-		return 302;
-
-	} elsif ($base_format eq 'marc21') {
-
-		my $ret = 200;    
-		try {
-			my $bib = $supercat->request( "open-ils.supercat.record.object.retrieve", $id )->gather(1)->[0];
+    my $apache = shift;
+    return Apache2::Const::DECLINED if (-e $apache->filename);
+
+    my $cgi = new CGI;
+
+    my $add_path = 0;
+    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+        my $rel_name = $cgi->url(-relative=>1);
+        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+    }
+
+    my $url = $cgi->url(-path_info=>$add_path);
+    my $root = (split 'supercat', $url)[0];
+    my $base = (split 'supercat', $url)[0] . 'supercat';
+    my $unapi = (split 'supercat', $url)[0] . 'unapi';
+
+    my $host = $cgi->virtual_host || $cgi->server_name;
+
+    my $path = $cgi->path_info;
+    my ($id,$type,$format,$command) = reverse split '/', $path;
+    my $flesh_feed = parse_feed_type($format);
+    (my $base_format = $format) =~ s/(-full|-uris)$//o;
+
+    my $skin = $cgi->param('skin') || 'default';
+    my $locale = $cgi->param('locale') || 'en-US';
+
+    # Enable localized results of copy status, etc
+    $supercat->session_locale($locale);
+    
+    if ( $path =~ m{^/formats(?:/([^\/]+))?$}o ) {
+        print "Content-type: application/xml; charset=utf-8\n";
+        if ($1) {
+            my $list = $supercat
+                ->request("open-ils.supercat.$1.formats")
+                ->gather(1);
+
+            print "\n";
+
+            print "<formats>
+                   <format>
+                     <name>opac</name>
+                     <type>text/html</type>
+                   </format>";
+
+            if ($1 eq 'record' or $1 eq 'isbn') {
+                print "<format>
+                     <name>htmlholdings</name>
+                     <type>text/html</type>
+                   </format>
+                   <format>
+                     <name>html</name>
+                     <type>text/html</type>
+                   </format>
+                   <format>
+                     <name>htmlholdings-full</name>
+                     <type>text/html</type>
+                   </format>
+                   <format>
+                     <name>html-full</name>
+                     <type>text/html</type>
+                   </format>
+                   <format>
+                     <name>marctxt</name>
+                     <type>text/plain</type>
+                   </format>
+                   <format>
+                     <name>ris</name>
+                     <type>text/plain</type>
+                   </format>";
+            }
+
+            for my $h (@$list) {
+                my ($type) = keys %$h;
+                print supercat_format($h, $type);
+
+                if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
+                    print supercat_format($h, "$type-full");
+                    print supercat_format($h, "$type-uris");
+                }
+
+            }
+
+            print "</formats>\n";
+
+            return Apache2::Const::OK;
+        }
+
+        my $list = $supercat
+            ->request("open-ils.supercat.record.formats")
+            ->gather(1);
+                
+        push @$list,
+            @{ $supercat
+                ->request("open-ils.supercat.metarecord.formats")
+                ->gather(1);
+            };
+
+        my %hash = map { ( (keys %$_)[0] => (values %$_)[0] ) } @$list;
+        $list = [ map { { $_ => $hash{$_} } } sort keys %hash ];
+
+        print "\n<formats>
+               <format>
+                 <name>opac</name>
+                 <type>text/html</type>
+               </format>
+               <format>
+                 <name>htmlholdings</name>
+                 <type>text/html</type>
+               </format>
+               <format>
+                 <name>html</name>
+                 <type>text/html</type>
+               </format>
+               <format>
+                 <name>htmlholdings-full</name>
+                 <type>text/html</type>
+               </format>
+               <format>
+                 <name>html-full</name>
+                 <type>text/html</type>
+               </format>
+               <format>
+                 <name>marctxt</name>
+                 <type>text/plain</type>
+               </format>
+               <format>
+                 <name>ris</name>
+                 <type>text/plain</type>
+               </format>";
+
+        for my $h (@$list) {
+            my ($type) = keys %$h;
+            print supercat_format($h, $type);
+
+            if (OpenILS::WWW::SuperCat::Feed->exists($type)) {
+                print supercat_format($h, "$type-full");
+                print supercat_format($h, "$type-uris");
+            }
+
+        }
+
+        print "</formats>\n";
+
+
+        return Apache2::Const::OK;
+    }
+
+    if ($format eq 'opac') {
+        print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?m=$id\n\n"
+            if ($type eq 'metarecord');
+        print "Location: $root/../../$locale/skin/$skin/xml/rdetail.xml?r=$id\n\n"
+            if ($type eq 'record');
+        return 302;
+
+    } elsif ($base_format eq 'marc21') {
+
+        my $ret = 200;    
+        try {
+            my $bib = $supercat->request( "open-ils.supercat.record.object.retrieve", $id )->gather(1)->[0];
         
-			print "Content-type: application/octet-stream\n\n" . MARC::Record->new_from_xml( $bib->marc, 'UTF-8', 'USMARC' )->as_usmarc;
-
-		} otherwise {
-			warn shift();
-			
-			print "Content-type: text/html; charset=utf-8\n\n";
-			$apache->custom_response( 404, <<"			HTML");
-			<html>
-				<head>
-					<title>ERROR</title>
-				</head>
-				<body>
-					<br/>
-					<center>Couldn't fetch $id as MARC21.</center>
-				</body>
-			</html>
-			HTML
-			$ret = 404;
-		};
-
-		return Apache2::Const::OK;
-
-	} elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format)) {
-		my $feed = create_record_feed(
-			$type,
-			$format => [ $id ],
-			undef, undef, undef,
-			$flesh_feed
-		);
-
-		$feed->root($root);
-		$feed->creator($host);
-
-		$feed->update_ts();
-
-		$feed->link( unapi => $base) if ($flesh_feed);
-
-		print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
-		print $U->entityize($feed->toString) . "\n";
-
-		return Apache2::Const::OK;
-	}
-
-	my $req = $supercat->request("open-ils.supercat.$type.$format.$command",$id);
-	$req->wait_complete;
-
-	if ($req->failed) {
-		print "Content-type: text/html; charset=utf-8\n\n";
-		$apache->custom_response( 404, <<"		HTML");
-		<html>
-			<head>
-				<title>$type $id not found!</title>
-			</head>
-			<body>
-				<br/>
-				<center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
-			</body>
-		</html>
-		HTML
-		return 404;
-	}
-
-	print "Content-type: application/xml; charset=utf-8\n\n";
-	print $U->entityize( $parser->parse_string( $req->gather(1) )->documentElement->toString );
-
-	return Apache2::Const::OK;
+            print "Content-type: application/octet-stream\n\n" . MARC::Record->new_from_xml( $bib->marc, 'UTF-8', 'USMARC' )->as_usmarc;
+
+        } otherwise {
+            warn shift();
+            
+            print "Content-type: text/html; charset=utf-8\n\n";
+            $apache->custom_response( 404, <<"            HTML");
+            <html>
+                <head>
+                    <title>ERROR</title>
+                </head>
+                <body>
+                    <br/>
+                    <center>Couldn't fetch $id as MARC21.</center>
+                </body>
+            </html>
+            HTML
+            $ret = 404;
+        };
+
+        return Apache2::Const::OK;
+
+    } elsif (OpenILS::WWW::SuperCat::Feed->exists($base_format)) {
+        my $feed = create_record_feed(
+            $type,
+            $format => [ $id ],
+            undef, undef, undef,
+            $flesh_feed
+        );
+
+        $feed->root($root);
+        $feed->creator($host);
+
+        $feed->update_ts();
+
+        $feed->link( unapi => $base) if ($flesh_feed);
+
+        print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
+        print $U->entityize($feed->toString) . "\n";
+
+        return Apache2::Const::OK;
+    }
+
+    my $req = $supercat->request("open-ils.supercat.$type.$format.$command",$id);
+    $req->wait_complete;
+
+    if ($req->failed) {
+        print "Content-type: text/html; charset=utf-8\n\n";
+        $apache->custom_response( 404, <<"        HTML");
+        <html>
+            <head>
+                <title>$type $id not found!</title>
+            </head>
+            <body>
+                <br/>
+                <center>Sorry, we couldn't $command a $type with the id of $id in format $format.</center>
+            </body>
+        </html>
+        HTML
+        return 404;
+    }
+
+    print "Content-type: application/xml; charset=utf-8\n\n";
+    print $U->entityize( $parser->parse_string( $req->gather(1) )->documentElement->toString );
+
+    return Apache2::Const::OK;
 }
 
 
 sub bookbag_feed {
-	my $apache = shift;
-	return Apache2::Const::DECLINED if (-e $apache->filename);
-
-	my $cgi = new CGI;
-
-	my $year = (gmtime())[5] + 1900;
-	my $host = $cgi->virtual_host || $cgi->server_name;
-
-	my $add_path = 0;
-	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-		my $rel_name = $cgi->url(-relative=>1);
-		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-	}
-
-	my $url = $cgi->url(-path_info=>$add_path);
-	my $root = (split 'feed', $url)[0] . '/';
-	my $base = (split 'bookbag', $url)[0] . '/bookbag';
-	my $unapi = (split 'feed', $url)[0] . '/unapi';
-
-	my $skin = $cgi->param('skin') || 'default';
-	my $locale = $cgi->param('locale') || 'en-US';
-	my $org = $cgi->param('searchOrg');
-
-	# Enable localized results of copy status, etc
-	$supercat->session_locale($locale);
-
-	my $org_unit = get_ou($org);
-	my $scope = "l=" . $org_unit->[0]->id . "&";
-
-	$root =~ s{(?<!http:)//}{/}go;
-	$base =~ s{(?<!http:)//}{/}go;
-	$unapi =~ s{(?<!http:)//}{/}go;
-
-	my $path = $cgi->path_info;
-	#warn "URL breakdown: $url -> $root -> $base -> $path -> $unapi";
-
-	my ($id,$type) = reverse split '/', $path;
-	my $flesh_feed = parse_feed_type($type);
-
-	my $bucket = $actor->request("open-ils.actor.container.public.flesh", 'biblio', $id)->gather(1);
-	return Apache2::Const::NOT_FOUND unless($bucket);
-
-	my $bucket_tag = "tag:$host,$year:record_bucket/$id";
-	if ($type eq 'opac') {
-		print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
-			join('&', map { "rl=" . $_->target_biblio_record_entry } @{ $bucket->items }) .
-			"\n\n";
-		return 302;
-	}
-
-	my $feed = create_record_feed(
-		'record',
-		$type,
-		[ map { $_->target_biblio_record_entry } @{ $bucket->items } ],
-		$unapi,
-		$org_unit->[0]->shortname,
-		undef,
-		$flesh_feed
-	);
-	$feed->root($root);
-	$feed->id($bucket_tag);
-
-	$feed->title("Items in Book Bag [".$bucket->name."]");
-	$feed->creator($host);
-	$feed->update_ts();
-
-	$feed->link(alternate => $base . "/rss2-full/$id" => 'application/rss+xml');
-	$feed->link(atom => $base . "/atom-full/$id" => 'application/atom+xml');
-	$feed->link(html => $base . "/html-full/$id" => 'text/html');
-	$feed->link(unapi => $unapi);
-
-	$feed->link(
-		OPAC =>
-		"http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
-			join('&', map { 'rl=' . $_->target_biblio_record_entry } @{$bucket->items} ),
-		'text/html'
-	);
-
-
-	print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
-	print $U->entityize($feed->toString) . "\n";
-
-	return Apache2::Const::OK;
+    my $apache = shift;
+    return Apache2::Const::DECLINED if (-e $apache->filename);
+
+    my $cgi = new CGI;
+
+    my $year = (gmtime())[5] + 1900;
+    my $host = $cgi->virtual_host || $cgi->server_name;
+
+    my $add_path = 0;
+    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+        my $rel_name = $cgi->url(-relative=>1);
+        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+    }
+
+    my $url = $cgi->url(-path_info=>$add_path);
+    my $root = (split 'feed', $url)[0] . '/';
+    my $base = (split 'bookbag', $url)[0] . '/bookbag';
+    my $unapi = (split 'feed', $url)[0] . '/unapi';
+
+    my $skin = $cgi->param('skin') || 'default';
+    my $locale = $cgi->param('locale') || 'en-US';
+    my $org = $cgi->param('searchOrg');
+
+    # Enable localized results of copy status, etc
+    $supercat->session_locale($locale);
+
+    my $org_unit = get_ou($org);
+    my $scope = "l=" . $org_unit->[0]->id . "&";
+
+    $root =~ s{(?<!http:)//}{/}go;
+    $base =~ s{(?<!http:)//}{/}go;
+    $unapi =~ s{(?<!http:)//}{/}go;
+
+    my $path = $cgi->path_info;
+    #warn "URL breakdown: $url -> $root -> $base -> $path -> $unapi";
+
+    my ($id,$type) = reverse split '/', $path;
+    my $flesh_feed = parse_feed_type($type);
+
+    my $bucket = $actor->request("open-ils.actor.container.public.flesh", 'biblio', $id)->gather(1);
+    return Apache2::Const::NOT_FOUND unless($bucket);
+
+    my $bucket_tag = "tag:$host,$year:record_bucket/$id";
+    if ($type eq 'opac') {
+        print "Location: $root/../../$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
+            join('&', map { "rl=" . $_->target_biblio_record_entry } @{ $bucket->items }) .
+            "\n\n";
+        return 302;
+    }
+
+    my $feed = create_record_feed(
+        'record',
+        $type,
+        [ map { $_->target_biblio_record_entry } @{ $bucket->items } ],
+        $unapi,
+        $org_unit->[0]->shortname,
+        undef,
+        $flesh_feed
+    );
+    $feed->root($root);
+    $feed->id($bucket_tag);
+
+    $feed->title("Items in Book Bag [".$bucket->name."]");
+    $feed->creator($host);
+    $feed->update_ts();
+
+    $feed->link(alternate => $base . "/rss2-full/$id" => 'application/rss+xml');
+    $feed->link(atom => $base . "/atom-full/$id" => 'application/atom+xml');
+    $feed->link(html => $base . "/html-full/$id" => 'text/html');
+    $feed->link(unapi => $unapi);
+
+    $feed->link(
+        OPAC =>
+        "http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
+            join('&', map { 'rl=' . $_->target_biblio_record_entry } @{$bucket->items} ),
+        'text/html'
+    );
+
+
+    print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
+    print $U->entityize($feed->toString) . "\n";
+
+    return Apache2::Const::OK;
 }
 
 sub changes_feed {
-	my $apache = shift;
-	return Apache2::Const::DECLINED if (-e $apache->filename);
+    my $apache = shift;
+    return Apache2::Const::DECLINED if (-e $apache->filename);
 
-	my $cgi = new CGI;
+    my $cgi = new CGI;
 
-	my $year = (gmtime())[5] + 1900;
-	my $host = $cgi->virtual_host || $cgi->server_name;
+    my $year = (gmtime())[5] + 1900;
+    my $host = $cgi->virtual_host || $cgi->server_name;
 
-	my $add_path = 0;
-	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-		my $rel_name = $cgi->url(-relative=>1);
-		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-	}
+    my $add_path = 0;
+    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+        my $rel_name = $cgi->url(-relative=>1);
+        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+    }
 
-	my $url = $cgi->url(-path_info=>$add_path);
-	my $root = (split 'feed', $url)[0];
-	my $base = (split 'freshmeat', $url)[0] . '/freshmeat';
-	my $unapi = (split 'feed', $url)[0] . 'unapi';
+    my $url = $cgi->url(-path_info=>$add_path);
+    my $root = (split 'feed', $url)[0];
+    my $base = (split 'freshmeat', $url)[0] . '/freshmeat';
+    my $unapi = (split 'feed', $url)[0] . 'unapi';
 
-	my $skin = $cgi->param('skin') || 'default';
-	my $locale = $cgi->param('locale') || 'en-US';
-	my $org = $cgi->param('searchOrg');
+    my $skin = $cgi->param('skin') || 'default';
+    my $locale = $cgi->param('locale') || 'en-US';
+    my $org = $cgi->param('searchOrg');
 
-	# Enable localized results of copy status, etc
-	$supercat->session_locale($locale);
+    # Enable localized results of copy status, etc
+    $supercat->session_locale($locale);
 
-	my $org_unit = get_ou($org);
-	my $scope = "l=" . $org_unit->[0]->id . "&";
+    my $org_unit = get_ou($org);
+    my $scope = "l=" . $org_unit->[0]->id . "&";
 
-	my $path = $cgi->path_info;
-	#warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
+    my $path = $cgi->path_info;
+    #warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
 
-	$path =~ s/^\/(?:feed\/)?freshmeat\///og;
-	
-	my ($type,$rtype,$axis,$limit,$date) = split '/', $path;
-	my $flesh_feed = parse_feed_type($type);
+    $path =~ s/^\/(?:feed\/)?freshmeat\///og;
+    
+    my ($type,$rtype,$axis,$limit,$date) = split '/', $path;
+    my $flesh_feed = parse_feed_type($type);
 
-	$limit ||= 10;
-	$limit = 10 if $limit !~ /^\d+$/;
+    $limit ||= 10;
+    $limit = 10 if $limit !~ /^\d+$/;
 
-	my $list = $supercat->request("open-ils.supercat.$rtype.record.$axis.recent", $date, $limit)->gather(1);
+    my $list = $supercat->request("open-ils.supercat.$rtype.record.$axis.recent", $date, $limit)->gather(1);
 
-	#if ($type eq 'opac') {
-	#	print "Location: $root/../../en-US/skin/default/xml/rresult.xml?rt=list&" .
-	#		join('&', map { "rl=" . $_ } @$list) .
-	#		"\n\n";
-	#	return 302;
-	#}
+    #if ($type eq 'opac') {
+    #    print "Location: $root/../../en-US/skin/default/xml/rresult.xml?rt=list&" .
+    #        join('&', map { "rl=" . $_ } @$list) .
+    #        "\n\n";
+    #    return 302;
+    #}
 
-	my $search = 'record';
-	if ($rtype eq 'authority') {
-		$search = 'authority';
-	}
-	my $feed = create_record_feed( $search, $type, $list, $unapi, $org_unit->[0]->shortname, undef, $flesh_feed);
-	$feed->root($root);
+    my $search = 'record';
+    if ($rtype eq 'authority') {
+        $search = 'authority';
+    }
+    my $feed = create_record_feed( $search, $type, $list, $unapi, $org_unit->[0]->shortname, undef, $flesh_feed);
+    $feed->root($root);
 
-	if ($date) {
-		$feed->title("Up to $limit recent $rtype ${axis}s from $date forward");
-	} else {
-		$feed->title("$limit most recent $rtype ${axis}s");
-	}
+    if ($date) {
+        $feed->title("Up to $limit recent $rtype ${axis}s from $date forward");
+    } else {
+        $feed->title("$limit most recent $rtype ${axis}s");
+    }
 
-	$feed->creator($host);
-	$feed->update_ts();
+    $feed->creator($host);
+    $feed->update_ts();
 
-	$feed->link(alternate => $base . "/rss2-full/$rtype/$axis/$limit/$date" => 'application/rss+xml');
-	$feed->link(atom => $base . "/atom-full/$rtype/$axis/$limit/$date" => 'application/atom+xml');
-	$feed->link(html => $base . "/html-full/$rtype/$axis/$limit/$date" => 'text/html');
-	$feed->link(unapi => $unapi);
+    $feed->link(alternate => $base . "/rss2-full/$rtype/$axis/$limit/$date" => 'application/rss+xml');
+    $feed->link(atom => $base . "/atom-full/$rtype/$axis/$limit/$date" => 'application/atom+xml');
+    $feed->link(html => $base . "/html-full/$rtype/$axis/$limit/$date" => 'text/html');
+    $feed->link(unapi => $unapi);
 
-	$feed->link(
-		OPAC =>
-		"http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
-			join('&', map { 'rl=' . $_} @$list ),
-		'text/html'
-	);
+    $feed->link(
+        OPAC =>
+        "http://$host/opac/$locale/skin/$skin/xml/rresult.xml?$scope" . "rt=list&" .
+            join('&', map { 'rl=' . $_} @$list ),
+        'text/html'
+    );
 
 
-	print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
-	print $U->entityize($feed->toString) . "\n";
+    print "Content-type: ". $feed->type ."; charset=utf-8\n\n";
+    print $U->entityize($feed->toString) . "\n";
 
-	return Apache2::Const::OK;
+    return Apache2::Const::OK;
 }
 
 sub opensearch_osd {
-	my $version = shift;
-	my $lib = shift;
-	my $class = shift;
-	my $base = shift;
+    my $version = shift;
+    my $lib = shift;
+    my $class = shift;
+    my $base = shift;
 
-	if ($version eq '1.0') {
-		print <<OSD;
+    if ($version eq '1.0') {
+        print <<OSD;
 Content-type: application/opensearchdescription+xml; charset=utf-8
 
 <?xml version="1.0" encoding="UTF-8"?>
@@ -1057,8 +1057,8 @@ Content-type: application/opensearchdescription+xml; charset=utf-8
   <AdultContent>false</AdultContent>
 </OpenSearchDescription>
 OSD
-	} else {
-		print <<OSD;
+    } else {
+        print <<OSD;
 Content-type: application/opensearchdescription+xml; charset=utf-8
 
 <?xml version="1.0" encoding="UTF-8"?>
@@ -1089,262 +1089,262 @@ Content-type: application/opensearchdescription+xml; charset=utf-8
   <InputEncoding>UTF-8</InputEncoding>
 </OpenSearchDescription>
 OSD
-	}
+    }
 
-	return Apache2::Const::OK;
+    return Apache2::Const::OK;
 }
 
 sub opensearch_feed {
-	my $apache = shift;
-	return Apache2::Const::DECLINED if (-e $apache->filename);
-
-	my $cgi = new CGI;
-	my $year = (gmtime())[5] + 1900;
-
-	my $host = $cgi->virtual_host || $cgi->server_name;
-
-	my $add_path = 0;
-	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-		my $rel_name = $cgi->url(-relative=>1);
-		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-	}
+    my $apache = shift;
+    return Apache2::Const::DECLINED if (-e $apache->filename);
 
-	my $url = $cgi->url(-path_info=>$add_path);
-	my $root = (split 'opensearch', $url)[0];
-	my $base = (split 'opensearch', $url)[0] . 'opensearch';
-	my $unapi = (split 'opensearch', $url)[0] . 'unapi';
+    my $cgi = new CGI;
+    my $year = (gmtime())[5] + 1900;
 
-	my $path = $cgi->path_info;
-	#warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
-
-	if ($path =~ m{^/?(1\.\d{1})/(?:([^/]+)/)?([^/]+)/osd.xml}o) {
-		
-		my $version = $1;
-		my $lib = uc($2);
-		my $class = $3;
-
-		if (!$lib || $lib eq '-') {
-		 	$lib = $actor->request(
-				'open-ils.actor.org_unit_list.search' => parent_ou => undef
-			)->gather(1)->[0]->shortname;
-		}
-
-		if ($class eq '-') {
-			$class = 'keyword';
-		}
-
-		return opensearch_osd($version, $lib, $class, $base);
-	}
-
-
-	my $page = $cgi->param('startPage') || 1;
-	my $offset = $cgi->param('startIndex') || 1;
-	my $limit = $cgi->param('count') || 10;
-
-	$page = 1 if ($page !~ /^\d+$/);
-	$offset = 1 if ($offset !~ /^\d+$/);
-	$limit = 10 if ($limit !~ /^\d+$/); $limit = 25 if ($limit > 25);
-
-	if ($page > 1) {
-		$offset = ($page - 1) * $limit;
-	} else {
-		$offset -= 1;
-	}
-
-	my ($version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = ('','','','','','','','');
-	(undef,$version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = split '/', $path;
-
-	$lang = $cgi->param('searchLang') if $cgi->param('searchLang');
-	$lang = '' if ($lang eq '*');
-
-	$sort = $cgi->param('searchSort') if $cgi->param('searchSort');
-	$sort ||= '';
-	$sortdir = $cgi->param('searchSortDir') if $cgi->param('searchSortDir');
-	$sortdir ||= '';
-
-	$terms .= " " if ($terms && $cgi->param('searchTerms'));
-	$terms .= $cgi->param('searchTerms') if $cgi->param('searchTerms');
-
-	$class = $cgi->param('searchClass') if $cgi->param('searchClass');
-	$class ||= '-';
-
-	$type = $cgi->param('responseType') if $cgi->param('responseType');
-	$type ||= '-';
-
-	$org = $cgi->param('searchOrg') if $cgi->param('searchOrg');
-	$org ||= '-';
-
-
-	my $kwt = $cgi->param('kw');
-	my $tit = $cgi->param('ti');
-	my $aut = $cgi->param('au');
-	my $sut = $cgi->param('su');
-	my $set = $cgi->param('se');
-
-	$terms .= " " if ($terms && $kwt);
-	$terms .= "keyword: $kwt" if ($kwt);
-	$terms .= " " if ($terms && $tit);
-	$terms .= "title: $tit" if ($tit);
-	$terms .= " " if ($terms && $aut);
-	$terms .= "author: $aut" if ($aut);
-	$terms .= " " if ($terms && $sut);
-	$terms .= "subject: $sut" if ($sut);
-	$terms .= " " if ($terms && $set);
-	$terms .= "series: $set" if ($set);
-
-	if ($version eq '1.0') {
-		$type = 'rss2';
-	} elsif ($type eq '-') {
-		$type = 'atom';
-	}
-	my $flesh_feed = parse_feed_type($type);
-
-	$terms = decode_utf8($terms);
-	$lang = 'eng' if ($lang eq 'en-US');
-
-	$log->debug("OpenSearch terms: $terms");
-
-	my $org_unit = get_ou($org);
-
-	# Apostrophes break search and get indexed as spaces anyway
-	my $safe_terms = $terms;
-	$safe_terms =~ s{'}{ }go;
-
-	my $recs = $search->request(
-		'open-ils.search.biblio.multiclass.query' => {
-			org_unit	=> $org_unit->[0]->id,
-			offset		=> $offset,
-			limit		=> $limit,
-			sort		=> $sort,
-			sort_dir	=> $sortdir,
+    my $host = $cgi->virtual_host || $cgi->server_name;
+
+    my $add_path = 0;
+    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+        my $rel_name = $cgi->url(-relative=>1);
+        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+    }
+
+    my $url = $cgi->url(-path_info=>$add_path);
+    my $root = (split 'opensearch', $url)[0];
+    my $base = (split 'opensearch', $url)[0] . 'opensearch';
+    my $unapi = (split 'opensearch', $url)[0] . 'unapi';
+
+    my $path = $cgi->path_info;
+    #warn "URL breakdown: $url ($rel_name) -> $root -> $base -> $path -> $unapi";
+
+    if ($path =~ m{^/?(1\.\d{1})/(?:([^/]+)/)?([^/]+)/osd.xml}o) {
+        
+        my $version = $1;
+        my $lib = uc($2);
+        my $class = $3;
+
+        if (!$lib || $lib eq '-') {
+             $lib = $actor->request(
+                'open-ils.actor.org_unit_list.search' => parent_ou => undef
+            )->gather(1)->[0]->shortname;
+        }
+
+        if ($class eq '-') {
+            $class = 'keyword';
+        }
+
+        return opensearch_osd($version, $lib, $class, $base);
+    }
+
+
+    my $page = $cgi->param('startPage') || 1;
+    my $offset = $cgi->param('startIndex') || 1;
+    my $limit = $cgi->param('count') || 10;
+
+    $page = 1 if ($page !~ /^\d+$/);
+    $offset = 1 if ($offset !~ /^\d+$/);
+    $limit = 10 if ($limit !~ /^\d+$/); $limit = 25 if ($limit > 25);
+
+    if ($page > 1) {
+        $offset = ($page - 1) * $limit;
+    } else {
+        $offset -= 1;
+    }
+
+    my ($version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = ('','','','','','','','');
+    (undef,$version,$org,$type,$class,$terms,$sort,$sortdir,$lang) = split '/', $path;
+
+    $lang = $cgi->param('searchLang') if $cgi->param('searchLang');
+    $lang = '' if ($lang eq '*');
+
+    $sort = $cgi->param('searchSort') if $cgi->param('searchSort');
+    $sort ||= '';
+    $sortdir = $cgi->param('searchSortDir') if $cgi->param('searchSortDir');
+    $sortdir ||= '';
+
+    $terms .= " " if ($terms && $cgi->param('searchTerms'));
+    $terms .= $cgi->param('searchTerms') if $cgi->param('searchTerms');
+
+    $class = $cgi->param('searchClass') if $cgi->param('searchClass');
+    $class ||= '-';
+
+    $type = $cgi->param('responseType') if $cgi->param('responseType');
+    $type ||= '-';
+
+    $org = $cgi->param('searchOrg') if $cgi->param('searchOrg');
+    $org ||= '-';
+
+
+    my $kwt = $cgi->param('kw');
+    my $tit = $cgi->param('ti');
+    my $aut = $cgi->param('au');
+    my $sut = $cgi->param('su');
+    my $set = $cgi->param('se');
+
+    $terms .= " " if ($terms && $kwt);
+    $terms .= "keyword: $kwt" if ($kwt);
+    $terms .= " " if ($terms && $tit);
+    $terms .= "title: $tit" if ($tit);
+    $terms .= " " if ($terms && $aut);
+    $terms .= "author: $aut" if ($aut);
+    $terms .= " " if ($terms && $sut);
+    $terms .= "subject: $sut" if ($sut);
+    $terms .= " " if ($terms && $set);
+    $terms .= "series: $set" if ($set);
+
+    if ($version eq '1.0') {
+        $type = 'rss2';
+    } elsif ($type eq '-') {
+        $type = 'atom';
+    }
+    my $flesh_feed = parse_feed_type($type);
+
+    $terms = decode_utf8($terms);
+    $lang = 'eng' if ($lang eq 'en-US');
+
+    $log->debug("OpenSearch terms: $terms");
+
+    my $org_unit = get_ou($org);
+
+    # Apostrophes break search and get indexed as spaces anyway
+    my $safe_terms = $terms;
+    $safe_terms =~ s{'}{ }go;
+
+    my $recs = $search->request(
+        'open-ils.search.biblio.multiclass.query' => {
+            org_unit    => $org_unit->[0]->id,
+            offset        => $offset,
+            limit        => $limit,
+            sort        => $sort,
+            sort_dir    => $sortdir,
             default_class => $class,
-			($lang ?    ( 'language' => $lang    ) : ()),
-		} => $safe_terms => 1
-	)->gather(1);
-
-	$log->debug("Hits for [$terms]: $recs->{count}");
-
-	my $feed = create_record_feed(
-		'record',
-		$type,
-		[ map { $_->[0] } @{$recs->{ids}} ],
-		$unapi,
-		$org,
-		undef,
-		$flesh_feed
-	);
-
-	$log->debug("Feed created...");
-
-	$feed->root($root);
-	$feed->lib($org);
-	$feed->search($safe_terms);
-	$feed->class($class);
-
-	$feed->title("Search results for [$terms] at ".$org_unit->[0]->name);
-
-	$feed->creator($host);
-	$feed->update_ts();
-
-	$feed->_create_node(
-		$feed->{item_xpath},
-		'http://a9.com/-/spec/opensearch/1.1/',
-		'totalResults',
-		$recs->{count},
-	);
-
-	$feed->_create_node(
-		$feed->{item_xpath},
-		'http://a9.com/-/spec/opensearch/1.1/',
-		'startIndex',
-		$offset + 1,
-	);
-
-	$feed->_create_node(
-		$feed->{item_xpath},
-		'http://a9.com/-/spec/opensearch/1.1/',
-		'itemsPerPage',
-		$limit,
-	);
-
-	$log->debug("...basic feed data added...");
-
-	$feed->link(
-		next =>
-		$base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int($offset + $limit + 1) . "&count=" . $limit =>
-		'application/opensearch+xml'
-	) if ($offset + $limit < $recs->{count});
-
-	$feed->link(
-		previous =>
-		$base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int(($offset - $limit) + 1) . "&count=" . $limit =>
-		'application/opensearch+xml'
-	) if ($offset);
-
-	$feed->link(
-		self =>
-		$base .  "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
-		'application/opensearch+xml'
-	);
-
-	$feed->link(
-		alternate =>
-		$base .  "/$version/$org/rss2-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
-		'application/rss+xml'
-	);
-
-	$feed->link(
-		atom =>
-		$base .  "/$version/$org/atom-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
-		'application/atom+xml'
-	);
-
-	$feed->link(
-		'html' =>
-		$base .  "/$version/$org/html/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
-		'text/html'
-	);
-
-	$feed->link(
-		'html-full' =>
-		$base .  "/$version/$org/html-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
-		'text/html'
-	);
-
-	$feed->link( 'unapi-server' => $unapi);
-
-	$log->debug("...feed links added...");
-
-#	$feed->link(
-#		opac =>
-#		$root . "../$lang/skin/default/xml/rresult.xml?rt=list&" .
-#			join('&', map { 'rl=' . $_->[0] } grep { ref $_ && defined $_->[0] } @{$recs->{ids}} ),
-#		'text/html'
-#	);
-
-	#print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . entityize($feed->toString) . "\n";
-	print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . $feed->toString . "\n";
-
-	$log->debug("...and feed returned.");
-
-	return Apache2::Const::OK;
+            ($lang ?    ( 'language' => $lang    ) : ()),
+        } => $safe_terms => 1
+    )->gather(1);
+
+    $log->debug("Hits for [$terms]: $recs->{count}");
+
+    my $feed = create_record_feed(
+        'record',
+        $type,
+        [ map { $_->[0] } @{$recs->{ids}} ],
+        $unapi,
+        $org,
+        undef,
+        $flesh_feed
+    );
+
+    $log->debug("Feed created...");
+
+    $feed->root($root);
+    $feed->lib($org);
+    $feed->search($safe_terms);
+    $feed->class($class);
+
+    $feed->title("Search results for [$terms] at ".$org_unit->[0]->name);
+
+    $feed->creator($host);
+    $feed->update_ts();
+
+    $feed->_create_node(
+        $feed->{item_xpath},
+        'http://a9.com/-/spec/opensearch/1.1/',
+        'totalResults',
+        $recs->{count},
+    );
+
+    $feed->_create_node(
+        $feed->{item_xpath},
+        'http://a9.com/-/spec/opensearch/1.1/',
+        'startIndex',
+        $offset + 1,
+    );
+
+    $feed->_create_node(
+        $feed->{item_xpath},
+        'http://a9.com/-/spec/opensearch/1.1/',
+        'itemsPerPage',
+        $limit,
+    );
+
+    $log->debug("...basic feed data added...");
+
+    $feed->link(
+        next =>
+        $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int($offset + $limit + 1) . "&count=" . $limit =>
+        'application/opensearch+xml'
+    ) if ($offset + $limit < $recs->{count});
+
+    $feed->link(
+        previous =>
+        $base . "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang&startIndex=" . int(($offset - $limit) + 1) . "&count=" . $limit =>
+        'application/opensearch+xml'
+    ) if ($offset);
+
+    $feed->link(
+        self =>
+        $base .  "/$version/$org/$type/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
+        'application/opensearch+xml'
+    );
+
+    $feed->link(
+        alternate =>
+        $base .  "/$version/$org/rss2-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
+        'application/rss+xml'
+    );
+
+    $feed->link(
+        atom =>
+        $base .  "/$version/$org/atom-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
+        'application/atom+xml'
+    );
+
+    $feed->link(
+        'html' =>
+        $base .  "/$version/$org/html/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
+        'text/html'
+    );
+
+    $feed->link(
+        'html-full' =>
+        $base .  "/$version/$org/html-full/$class?searchTerms=$terms&searchSort=$sort&searchSortDir=$sortdir&searchLang=$lang" =>
+        'text/html'
+    );
+
+    $feed->link( 'unapi-server' => $unapi);
+
+    $log->debug("...feed links added...");
+
+#    $feed->link(
+#        opac =>
+#        $root . "../$lang/skin/default/xml/rresult.xml?rt=list&" .
+#            join('&', map { 'rl=' . $_->[0] } grep { ref $_ && defined $_->[0] } @{$recs->{ids}} ),
+#        'text/html'
+#    );
+
+    #print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . entityize($feed->toString) . "\n";
+    print $cgi->header( -type => $feed->type, -charset => 'UTF-8') . $feed->toString . "\n";
+
+    $log->debug("...and feed returned.");
+
+    return Apache2::Const::OK;
 }
 
 sub create_record_feed {
-	my $search = shift;
-	my $type = shift;
-	my $records = shift;
-	my $unapi = shift;
+    my $search = shift;
+    my $type = shift;
+    my $records = shift;
+    my $unapi = shift;
 
-	my $lib = uc(shift()) || '-';
-	my $depth = shift;
-	my $flesh = shift;
+    my $lib = uc(shift()) || '-';
+    my $depth = shift;
+    my $flesh = shift;
 
-	my $paging = shift;
+    my $paging = shift;
 
-	my $cgi = new CGI;
-	my $base = $cgi->url;
-	my $host = $cgi->virtual_host || $cgi->server_name;
+    my $cgi = new CGI;
+    my $base = $cgi->url;
+    my $host = $cgi->virtual_host || $cgi->server_name;
 
     my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
     $year += 1900;
@@ -1352,253 +1352,253 @@ sub create_record_feed {
 
     my $tag_prefix = sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d", $month, $day);
 
-	my $flesh_feed = defined($flesh) ? $flesh : parse_feed_type($type);
+    my $flesh_feed = defined($flesh) ? $flesh : parse_feed_type($type);
 
-	$type =~ s/(-full|-uris)$//o;
+    $type =~ s/(-full|-uris)$//o;
 
-	my $feed = new OpenILS::WWW::SuperCat::Feed ($type);
-	$feed->base($base) if ($flesh);
-	$feed->unapi($unapi) if ($flesh);
+    my $feed = new OpenILS::WWW::SuperCat::Feed ($type);
+    $feed->base($base) if ($flesh);
+    $feed->unapi($unapi) if ($flesh);
 
-	$type = 'atom' if ($type eq 'html');
-	$type = 'marcxml' if (($type eq 'htmlholdings') || ($type eq 'marctxt') || ($type eq 'ris'));
+    $type = 'atom' if ($type eq 'html');
+    $type = 'marcxml' if (($type eq 'htmlholdings') || ($type eq 'marctxt') || ($type eq 'ris'));
 
-	#$records = $supercat->request( "open-ils.supercat.record.object.retrieve", $records )->gather(1);
+    #$records = $supercat->request( "open-ils.supercat.record.object.retrieve", $records )->gather(1);
 
-	my $count = 0;
-	for my $record (@$records) {
-		next unless($record);
+    my $count = 0;
+    for my $record (@$records) {
+        next unless($record);
 
-		#my $rec = $record->id;
-		my $rec = $record;
+        #my $rec = $record->id;
+        my $rec = $record;
 
-		my $item_tag = "$tag_prefix:biblio-record_entry/$rec/$lib";
-		$item_tag = "$tag_prefix:metabib-metarecord/$rec/$lib" if ($search eq 'metarecord');
-		$item_tag = "$tag_prefix:isbn/$rec/$lib" if ($search eq 'isbn');
-		$item_tag .= "/$depth" if (defined($depth));
+        my $item_tag = "$tag_prefix:biblio-record_entry/$rec/$lib";
+        $item_tag = "$tag_prefix:metabib-metarecord/$rec/$lib" if ($search eq 'metarecord');
+        $item_tag = "$tag_prefix:isbn/$rec/$lib" if ($search eq 'isbn');
+        $item_tag .= "/$depth" if (defined($depth));
 
-		$item_tag = "$tag_prefix:authority-record_entry/$rec" if ($search eq 'authority');
+        $item_tag = "$tag_prefix:authority-record_entry/$rec" if ($search eq 'authority');
 
-		my $xml = $supercat->request(
-			"open-ils.supercat.$search.$type.retrieve",
-			$rec
-		)->gather(1);
-		next unless $xml;
+        my $xml = $supercat->request(
+            "open-ils.supercat.$search.$type.retrieve",
+            $rec
+        )->gather(1);
+        next unless $xml;
 
-		my $node = $feed->add_item($xml);
-		next unless $node;
+        my $node = $feed->add_item($xml);
+        next unless $node;
 
-		$xml = '';
-		if ($lib && ($type eq 'marcxml' || $type eq 'atom') &&  $flesh > 0) {
-			my $r = $supercat->request( "open-ils.supercat.$search.holdings_xml.retrieve", $rec, $lib, $depth, $flesh_feed, $paging );
-			while ( !$r->complete ) {
-				$xml .= join('', map {$_->content} $r->recv);
-			}
-			$xml .= join('', map {$_->content} $r->recv);
-			$node->add_holdings($xml);
-		}
+        $xml = '';
+        if ($lib && ($type eq 'marcxml' || $type eq 'atom') &&  $flesh > 0) {
+            my $r = $supercat->request( "open-ils.supercat.$search.holdings_xml.retrieve", $rec, $lib, $depth, $flesh_feed, $paging );
+            while ( !$r->complete ) {
+                $xml .= join('', map {$_->content} $r->recv);
+            }
+            $xml .= join('', map {$_->content} $r->recv);
+            $node->add_holdings($xml);
+        }
 
-		$node->id($item_tag);
-		#$node->update_ts(cleanse_ISO8601($record->edit_date));
-		$node->link(alternate => $feed->unapi . "?id=$item_tag&format=htmlholdings-full" => 'text/html') if ($flesh > 0);
-		$node->link(opac => $feed->unapi . "?id=$item_tag&format=opac") if ($flesh > 0);
-		$node->link(unapi => $feed->unapi . "?id=$item_tag") if ($flesh);
-		$node->link('unapi-id' => $item_tag) if ($flesh);
-	}
+        $node->id($item_tag);
+        #$node->update_ts(cleanse_ISO8601($record->edit_date));
+        $node->link(alternate => $feed->unapi . "?id=$item_tag&format=htmlholdings-full" => 'text/html') if ($flesh > 0);
+        $node->link(opac => $feed->unapi . "?id=$item_tag&format=opac") if ($flesh > 0);
+        $node->link(unapi => $feed->unapi . "?id=$item_tag") if ($flesh);
+        $node->link('unapi-id' => $item_tag) if ($flesh);
+    }
 
-	return $feed;
+    return $feed;
 }
 
 sub string_browse {
-	my $apache = shift;
-	return Apache2::Const::DECLINED if (-e $apache->filename);
+    my $apache = shift;
+    return Apache2::Const::DECLINED if (-e $apache->filename);
 
-	my $cgi = new CGI;
-	my $year = (gmtime())[5] + 1900;
+    my $cgi = new CGI;
+    my $year = (gmtime())[5] + 1900;
 
-	my $host = $cgi->virtual_host || $cgi->server_name;
+    my $host = $cgi->virtual_host || $cgi->server_name;
 
-	my $add_path = 0;
-	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-		my $rel_name = $cgi->url(-relative=>1);
-		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-	}
+    my $add_path = 0;
+    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+        my $rel_name = $cgi->url(-relative=>1);
+        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+    }
 
-	my $url = $cgi->url(-path_info=>$add_path);
-	my $root = (split 'browse', $url)[0];
-	my $base = (split 'browse', $url)[0] . 'browse';
-	my $unapi = (split 'browse', $url)[0] . 'unapi';
+    my $url = $cgi->url(-path_info=>$add_path);
+    my $root = (split 'browse', $url)[0];
+    my $base = (split 'browse', $url)[0] . 'browse';
+    my $unapi = (split 'browse', $url)[0] . 'unapi';
 
-	my $path = $cgi->path_info;
-	$path =~ s/^\///og;
+    my $path = $cgi->path_info;
+    $path =~ s/^\///og;
 
-	my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
-	#warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
+    my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
+    #warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
 
     return item_age_browse($apache) if ($axis eq 'item-age'); # short-circut to the item-age sub
 
-	my $status = [$cgi->param('status')];
-	my $cpLoc = [$cgi->param('copyLocation')];
-	$site ||= $cgi->param('searchOrg');
-	$page ||= $cgi->param('startPage') || 0;
-	$page_size ||= $cgi->param('count') || 9;
-
-	$page = 0 if ($page !~ /^-?\d+$/);
-	$page_size = 9 if $page_size !~ /^\d+$/;
-
-	my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
-	my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
-
-	unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
-		warn "something's wrong...";
-		warn " >>> format: $format -> axis: $axis -> site: $site -> string: $string -> page: $page -> page_size: $page_size ";
-		return undef;
-	}
-
-	$string = decode_utf8($string);
-	$string =~ s/\+/ /go;
-	$string =~ s/'//go;
-
-	my $tree = $supercat->request(
-		"open-ils.supercat.$axis.browse",
-		$string,
-		(($axis =~ /^authority/) ? () : ($site)),
-		$page_size,
-		$page,
-		$status,
-		$cpLoc
-	)->gather(1);
+    my $status = [$cgi->param('status')];
+    my $cpLoc = [$cgi->param('copyLocation')];
+    $site ||= $cgi->param('searchOrg');
+    $page ||= $cgi->param('startPage') || 0;
+    $page_size ||= $cgi->param('count') || 9;
+
+    $page = 0 if ($page !~ /^-?\d+$/);
+    $page_size = 9 if $page_size !~ /^\d+$/;
+
+    my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
+    my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
+
+    unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
+        warn "something's wrong...";
+        warn " >>> format: $format -> axis: $axis -> site: $site -> string: $string -> page: $page -> page_size: $page_size ";
+        return undef;
+    }
+
+    $string = decode_utf8($string);
+    $string =~ s/\+/ /go;
+    $string =~ s/'//go;
+
+    my $tree = $supercat->request(
+        "open-ils.supercat.$axis.browse",
+        $string,
+        (($axis =~ /^authority/) ? () : ($site)),
+        $page_size,
+        $page,
+        $status,
+        $cpLoc
+    )->gather(1);
 
     (my $norm_format = $format) =~ s/(-full|-uris)$//o;
 
-	my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
-	print $header.$content;
-	return Apache2::Const::OK;
+    my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
+    print $header.$content;
+    return Apache2::Const::OK;
 }
 
 sub string_startwith {
-	my $apache = shift;
-	return Apache2::Const::DECLINED if (-e $apache->filename);
-
-	my $cgi = new CGI;
-	my $year = (gmtime())[5] + 1900;
-
-	my $host = $cgi->virtual_host || $cgi->server_name;
-
-	my $add_path = 0;
-	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-		my $rel_name = $cgi->url(-relative=>1);
-		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-	}
-
-	my $url = $cgi->url(-path_info=>$add_path);
-	my $root = (split 'startwith', $url)[0];
-	my $base = (split 'startwith', $url)[0] . 'startwith';
-	my $unapi = (split 'startwith', $url)[0] . 'unapi';
-
-	my $path = $cgi->path_info;
-	$path =~ s/^\///og;
-
-	my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
-	#warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
-
-	my $status = [$cgi->param('status')];
-	my $cpLoc = [$cgi->param('copyLocation')];
-	$site ||= $cgi->param('searchOrg');
-	$page ||= $cgi->param('startPage') || 0;
-	$page_size ||= $cgi->param('count') || 9;
-
-	$page = 0 if ($page !~ /^-?\d+$/);
-	$page_size = 9 if $page_size !~ /^\d+$/;
-
-	my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
-	my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
-
-	unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
-		warn "something's wrong...";
-		warn " >>> format: $format -> axis: $axis -> site: $site -> string: $string -> page: $page -> page_size: $page_size ";
-		return undef;
-	}
-
-	$string = decode_utf8($string);
-	$string =~ s/\+/ /go;
-	$string =~ s/'//go;
-
-	my $tree = $supercat->request(
-		"open-ils.supercat.$axis.startwith",
-		$string,
-		(($axis =~ /^authority/) ? () : ($site)),
-		$page_size,
-		$page,
-		$status,
-		$cpLoc
-	)->gather(1);
+    my $apache = shift;
+    return Apache2::Const::DECLINED if (-e $apache->filename);
+
+    my $cgi = new CGI;
+    my $year = (gmtime())[5] + 1900;
+
+    my $host = $cgi->virtual_host || $cgi->server_name;
+
+    my $add_path = 0;
+    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+        my $rel_name = $cgi->url(-relative=>1);
+        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+    }
+
+    my $url = $cgi->url(-path_info=>$add_path);
+    my $root = (split 'startwith', $url)[0];
+    my $base = (split 'startwith', $url)[0] . 'startwith';
+    my $unapi = (split 'startwith', $url)[0] . 'unapi';
+
+    my $path = $cgi->path_info;
+    $path =~ s/^\///og;
+
+    my ($format,$axis,$site,$string,$page,$page_size) = split '/', $path;
+    #warn " >>> $format -> $axis -> $site -> $string -> $page -> $page_size ";
+
+    my $status = [$cgi->param('status')];
+    my $cpLoc = [$cgi->param('copyLocation')];
+    $site ||= $cgi->param('searchOrg');
+    $page ||= $cgi->param('startPage') || 0;
+    $page_size ||= $cgi->param('count') || 9;
+
+    $page = 0 if ($page !~ /^-?\d+$/);
+    $page_size = 9 if $page_size !~ /^\d+$/;
+
+    my $prev = join('/', $base,$format,$axis,$site,$string,$page - 1,$page_size);
+    my $next = join('/', $base,$format,$axis,$site,$string,$page + 1,$page_size);
+
+    unless ($string and $axis and grep { $axis eq $_ } keys %browse_types) {
+        warn "something's wrong...";
+        warn " >>> format: $format -> axis: $axis -> site: $site -> string: $string -> page: $page -> page_size: $page_size ";
+        return undef;
+    }
+
+    $string = decode_utf8($string);
+    $string =~ s/\+/ /go;
+    $string =~ s/'//go;
+
+    my $tree = $supercat->request(
+        "open-ils.supercat.$axis.startwith",
+        $string,
+        (($axis =~ /^authority/) ? () : ($site)),
+        $page_size,
+        $page,
+        $status,
+        $cpLoc
+    )->gather(1);
 
     (my $norm_format = $format) =~ s/(-full|-uris)$//o;
 
-	my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
-	print $header.$content;
-	return Apache2::Const::OK;
+    my ($header,$content) = $browse_types{$axis}{$norm_format}->($tree,$prev,$next,$format,$unapi,$base,$site);
+    print $header.$content;
+    return Apache2::Const::OK;
 }
 
 sub item_age_browse {
-	my $apache = shift;
-	return Apache2::Const::DECLINED if (-e $apache->filename);
-
-	my $cgi = new CGI;
-	my $year = (gmtime())[5] + 1900;
-
-	my $host = $cgi->virtual_host || $cgi->server_name;
-
-	my $add_path = 0;
-	if ( $cgi->server_software !~ m|^Apache/2.2| ) {
-		my $rel_name = $cgi->url(-relative=>1);
-		$add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
-	}
-
-	my $url = $cgi->url(-path_info=>$add_path);
-	my $root = (split 'browse', $url)[0];
-	my $base = (split 'browse', $url)[0] . 'browse';
-	my $unapi = (split 'browse', $url)[0] . 'unapi';
-
-	my $path = $cgi->path_info;
-	$path =~ s/^\///og;
-
-	my ($format,$axis,$site,$page,$page_size) = split '/', $path;
-	#warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
-
-	unless ($axis eq 'item-age') {
-		warn "something's wrong...";
-		warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
-		return undef;
-	}
-
-	my $status = [$cgi->param('status')];
-	my $cpLoc = [$cgi->param('copyLocation')];
-	$site ||= $cgi->param('searchOrg') || '-';
-	$page ||= $cgi->param('startPage') || 1;
-	$page_size ||= $cgi->param('count') || 10;
-
-	$page = 1 if ($page !~ /^-?\d+$/ || $page < 1);
-	$page_size = 10 if $page_size !~ /^\d+$/;
-
-	my $prev = join('/', $base,$format,$axis,$site,$page - 1,$page_size);
-	my $next = join('/', $base,$format,$axis,$site,$page + 1,$page_size);
-
-	my $recs = $supercat->request(
-		"open-ils.supercat.new_book_list",
-		$site,
-		$page_size,
-		$page,
-		$status,
-		$cpLoc
-	)->gather(1);
+    my $apache = shift;
+    return Apache2::Const::DECLINED if (-e $apache->filename);
+
+    my $cgi = new CGI;
+    my $year = (gmtime())[5] + 1900;
+
+    my $host = $cgi->virtual_host || $cgi->server_name;
+
+    my $add_path = 0;
+    if ( $cgi->server_software !~ m|^Apache/2.2| ) {
+        my $rel_name = $cgi->url(-relative=>1);
+        $add_path = 1 if ($cgi->url(-path_info=>1) !~ /$rel_name$/);
+    }
+
+    my $url = $cgi->url(-path_info=>$add_path);
+    my $root = (split 'browse', $url)[0];
+    my $base = (split 'browse', $url)[0] . 'browse';
+    my $unapi = (split 'browse', $url)[0] . 'unapi';
+
+    my $path = $cgi->path_info;
+    $path =~ s/^\///og;
+
+    my ($format,$axis,$site,$page,$page_size) = split '/', $path;
+    #warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
+
+    unless ($axis eq 'item-age') {
+        warn "something's wrong...";
+        warn " >>> $format -> $axis -> $site -> $page -> $page_size ";
+        return undef;
+    }
+
+    my $status = [$cgi->param('status')];
+    my $cpLoc = [$cgi->param('copyLocation')];
+    $site ||= $cgi->param('searchOrg') || '-';
+    $page ||= $cgi->param('startPage') || 1;
+    $page_size ||= $cgi->param('count') || 10;
+
+    $page = 1 if ($page !~ /^-?\d+$/ || $page < 1);
+    $page_size = 10 if $page_size !~ /^\d+$/;
+
+    my $prev = join('/', $base,$format,$axis,$site,$page - 1,$page_size);
+    my $next = join('/', $base,$format,$axis,$site,$page + 1,$page_size);
+
+    my $recs = $supercat->request(
+        "open-ils.supercat.new_book_list",
+        $site,
+        $page_size,
+        $page,
+        $status,
+        $cpLoc
+    )->gather(1);
 
     (my $norm_format = $format) =~ s/(-full|-uris)$//o;
 
-	my ($header,$content) = $browse_types{$axis}{$norm_format}->($recs,$prev,$next,$format,$unapi,$base,$site);
-	print $header.$content;
-	return Apache2::Const::OK;
+    my ($header,$content) = $browse_types{$axis}{$norm_format}->($recs,$prev,$next,$format,$unapi,$base,$site);
+    print $header.$content;
+    return Apache2::Const::OK;
 }
 
 our %qualifier_map = (
@@ -1687,91 +1687,91 @@ our %qualifier_map = (
 );
 
 our %qualifier_ids = (
-		eg => 'http://open-ils.org/spec/SRU/context-set/evergreen/v1',
-		dc => 'info:srw/cql-context-set/1/dc-v1.1',
-		bib => 'info:srw/cql-context-set/1/bib-v1.0',
-		srw	=> ''
+    eg  => 'http://open-ils.org/spec/SRU/context-set/evergreen/v1',
+    dc  => 'info:srw/cql-context-set/1/dc-v1.1',
+    bib => 'info:srw/cql-context-set/1/bib-v1.0',
+    srw => ''
 );
 
 our %nested_qualifier_map = (
-		eg => {
-			site		=> ['site','Evergreen Site Code (shortname)'],
-			sort		=> ['sort','Sort on relevance, title, author, pubdate, create_date or edit_date'],
-			direction	=> ['dir','Sort direction (asc|desc)'],
-			available	=> ['available','Filter to available (true|false)'],
-			title		=> ['title'],
-			author		=> ['author'],
-			name		=> ['author'],
-			subject		=> ['subject'],
-			keyword		=> ['keyword'],
-			series		=> ['series'],
-		},
-		dc => {
-			title		=> ['title'],
-			creator		=> ['author'],
-			contributor	=> ['author'],
-			publisher	=> ['keyword'],
-			subject		=> ['subject'],
-			identifier	=> ['keyword'],
-			type		=> [undef],
-			format		=> [undef],
-			language	=> ['lang'],
-		},
-		bib => {
-		# Title class:
-	        titleAbbreviated	=> ['title'],
-		    titleUniform		=> ['title'],
-			titleTranslated		=> ['title'],
-	        titleAlternative	=> ['title'],
-		    titleSeries			=> ['series'],
+        eg => {
+            site        => ['site','Evergreen Site Code (shortname)'],
+            sort        => ['sort','Sort on relevance, title, author, pubdate, create_date or edit_date'],
+            direction   => ['dir','Sort direction (asc|desc)'],
+            available   => ['available','Filter to available (true|false)'],
+            title       => ['title'],
+            author      => ['author'],
+            name        => ['author'],
+            subject     => ['subject'],
+            keyword     => ['keyword'],
+            series      => ['series'],
+        },
+        dc => {
+            title       => ['title'],
+            creator     => ['author'],
+            contributor => ['author'],
+            publisher   => ['keyword'],
+            subject     => ['subject'],
+            identifier  => ['keyword'],
+            type        => [undef],
+            format      => [undef],
+            language    => ['lang'],
+        },
+        bib => {
+        # Title class:
+            titleAbbreviated    => ['title'],
+            titleUniform        => ['title'],
+            titleTranslated     => ['title'],
+            titleAlternative    => ['title'],
+            titleSeries         => ['series'],
 
     # Author/Name class:
-			name				=> ['author'],
-			namePersonal		=> ['author'],
-			namePersonalFamily	=> ['author'],
-			namePersonalGiven	=> ['author'],
-			nameCorporate		=> ['author'],
-			nameConference		=> ['author'],
-
-		# Subject class:
-			subjectPlace		=> ['subject'],
-			subjectTitle		=> ['keyword'],
-			subjectName			=> ['subject|name'],
-			subjectOccupation	=> ['keyword'],
+            name                => ['author'],
+            namePersonal        => ['author'],
+            namePersonalFamily  => ['author'],
+            namePersonalGiven   => ['author'],
+            nameCorporate       => ['author'],
+            nameConference      => ['author'],
+
+        # Subject class:
+            subjectPlace        => ['subject'],
+            subjectTitle        => ['keyword'],
+            subjectName         => ['subject|name'],
+            subjectOccupation   => ['keyword'],
 
     # Keyword class:
 
     # Dates:
-			dateIssued			=> [undef],
-			dateCreated			=> [undef],
-			dateValid			=> [undef],
-			dateModified		=> [undef],
-			dateCopyright		=> [undef],
+            dateIssued          => [undef],
+            dateCreated         => [undef],
+            dateValid           => [undef],
+            dateModified        => [undef],
+            dateCopyright       => [undef],
 
     # Genre:
-			genre				=> ['keyword'],
+            genre               => ['keyword'],
 
     # Target Audience:
-			audience			=> [undef],
+            audience            => [undef],
 
     # Place of Origin:
-			originPlace			=> [undef],
+            originPlace         => [undef],
 
     # Edition
-			edition				=> ['keyword'],
+            edition             => ['keyword'],
 
     # Part:
-			volume				=> ['keyword'],
-			issue				=> ['keyword'],
-			startPage			=> ['keyword'],
-			endPage				=> ['keyword'],
+            volume              => ['keyword'],
+            issue               => ['keyword'],
+            startPage           => ['keyword'],
+            endPage             => ['keyword'],
 
     # Issuance:
-			issuance			=> ['keyword'],
-		},
-		srw	=> {
-			serverChoice		=> ['keyword'],
-		},
+            issuance            => ['keyword'],
+        },
+        srw    => {
+            serverChoice        => ['keyword'],
+        },
 );
 
 # Our authority search options are currently pretty impoverished;
@@ -1789,49 +1789,49 @@ our %nested_auth_qualifier_map = (
 
 my $base_explain = <<XML;
 <explain
-		id="evergreen-sru-explain-full"
-		authoritative="true"
-		xmlns:z="http://explain.z3950.org/dtd/2.0/"
-		xmlns="http://explain.z3950.org/dtd/2.0/">
-	<serverInfo transport="http" protocol="SRU" version="1.1">
-		<host/>
-		<port/>
-		<database/>
-	</serverInfo>
-
-	<databaseInfo>
-		<title primary="true"/>
-		<description primary="true"/>
-	</databaseInfo>
-
-	<indexInfo>
-		<set identifier="info:srw/cql-context-set/1/cql-v1.2" name="cql"/>
-	</indexInfo>
-
-	<schemaInfo>
-		<schema
-				identifier="info:srw/schema/1/marcxml-v1.1"
-				location="http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
-				sort="true"
-				retrieve="true"
-				name="marcxml">
-			<title>MARC21Slim (marcxml)</title>
-		</schema>
-	</schemaInfo>
-
-	<configInfo>
-		<default type="numberOfRecords">10</default>
-		<default type="contextSet">eg</default>
-		<default type="index">keyword</default>
-		<default type="relation">all</default>
-		<default type="sortSchema">marcxml</default>
-		<default type="retrieveSchema">marcxml</default>
-		<setting type="maximumRecords">50</setting>
-		<supports type="relationModifier">relevant</supports>
-		<supports type="relationModifier">stem</supports>
-		<supports type="relationModifier">fuzzy</supports>
-		<supports type="relationModifier">word</supports>
-	</configInfo>
+        id="evergreen-sru-explain-full"
+        authoritative="true"
+        xmlns:z="http://explain.z3950.org/dtd/2.0/"
+        xmlns="http://explain.z3950.org/dtd/2.0/">
+    <serverInfo transport="http" protocol="SRU" version="1.1">
+        <host/>
+        <port/>
+        <database/>
+    </serverInfo>
+
+    <databaseInfo>
+        <title primary="true"/>
+        <description primary="true"/>
+    </databaseInfo>
+
+    <indexInfo>
+        <set identifier="info:srw/cql-context-set/1/cql-v1.2" name="cql"/>
+    </indexInfo>
+
+    <schemaInfo>
+        <schema
+                identifier="info:srw/schema/1/marcxml-v1.1"
+                location="http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
+                sort="true"
+                retrieve="true"
+                name="marcxml">
+            <title>MARC21Slim (marcxml)</title>
+        </schema>
+    </schemaInfo>
+
+    <configInfo>
+        <default type="numberOfRecords">10</default>
+        <default type="contextSet">eg</default>
+        <default type="index">keyword</default>
+        <default type="relation">all</default>
+        <default type="sortSchema">marcxml</default>
+        <default type="retrieveSchema">marcxml</default>
+        <setting type="maximumRecords">50</setting>
+        <supports type="relationModifier">relevant</supports>
+        <supports type="relationModifier">stem</supports>
+        <supports type="relationModifier">fuzzy</supports>
+        <supports type="relationModifier">word</supports>
+    </configInfo>
 
 </explain>
 XML
@@ -1839,29 +1839,29 @@ XML
 
 my $ex_doc;
 sub sru_search {
-	my $cgi = new CGI;
+    my $cgi = new CGI;
 
-	my $req = SRU::Request->newFromCGI( $cgi );
-	my $resp = SRU::Response->newFromRequest( $req );
+    my $req = SRU::Request->newFromCGI( $cgi );
+    my $resp = SRU::Response->newFromRequest( $req );
 
-	# Find the org_unit shortname, if passed as part of the URL
-	# http://example.com/opac/extras/sru/SHORTNAME
-	my $url = $cgi->path_info;
-	my ($shortname, $holdings) = $url =~ m#/?([^/]*)(/holdings)?#;
+    # Find the org_unit shortname, if passed as part of the URL
+    # http://example.com/opac/extras/sru/SHORTNAME
+    my $url = $cgi->path_info;
+    my ($shortname, $holdings) = $url =~ m#/?([^/]*)(/holdings)?#;
 
-	if ( $resp->type eq 'searchRetrieve' ) {
+    if ( $resp->type eq 'searchRetrieve' ) {
 
-		# Older versions of Debian packages returned terms to us double-encoded,
-		# so we had to forcefully double-decode them a second time with
-		# an outer decode('utf8', $string) call; this seems to be resolved with
-		# Debian Lenny packages sometime between 2009-07-27 and 2010-02-15
-		my $cql_query = decode_utf8($req->query);
-		my $search_string = decode_utf8($req->cql->toEvergreen);
+        # Older versions of Debian packages returned terms to us double-encoded,
+        # so we had to forcefully double-decode them a second time with
+        # an outer decode('utf8', $string) call; this seems to be resolved with
+        # Debian Lenny packages sometime between 2009-07-27 and 2010-02-15
+        my $cql_query = decode_utf8($req->query);
+        my $search_string = decode_utf8($req->cql->toEvergreen);
 
-		# Ensure the search string overrides the default site
-		if ($shortname and $search_string !~ m#site:#) {
-			$search_string .= " site:$shortname";
-		}
+        # Ensure the search string overrides the default site
+        if ($shortname and $search_string !~ m#site:#) {
+            $search_string .= " site:$shortname";
+        }
 
         my $offset = $req->startRecord;
         $offset-- if ($offset);
@@ -1872,85 +1872,85 @@ sub sru_search {
 
         $log->info("SRU search string [$cql_query] converted to [$search_string]\n");
 
- 		my $recs = $search->request(
-			'open-ils.search.biblio.multiclass.query' => {offset => $offset, limit => $limit} => $search_string => 1
-		)->gather(1);
-
-		my $bre = $supercat->request( 'open-ils.supercat.record.object.retrieve' => [ map { $_->[0] } @{$recs->{ids}} ] )->gather(1);
-
-		foreach my $record (@$bre) {
-			my $marcxml = $record->marc;
-			# Make the beast conform to a VDX-supported format
-			# See http://vdxipedia.oclc.org/index.php/Holdings_Parsing
-			# Trying to implement LIBSOL_852_A format; so much for standards
-			if ($holdings) {
-				my $bib_holdings = $supercat->request('open-ils.supercat.record.basic_holdings.retrieve', $record->id, $shortname || '-')->gather(1);
-				my $marc = MARC::Record->new_from_xml($marcxml, 'UTF8', 'XML');
-
-				# Force record leader to 'a' as our data is always UTF8
-				# Avoids marc8_to_utf8 from being invoked with horrible results
-				# on the off-chance the record leader isn't correct
-				my $ldr = $marc->leader;
-				substr($ldr, 9, 1, 'a');
-				$marc->leader($ldr);
-
-				# Expects the record ID in the 001
-				$marc->delete_field($_) for ($marc->field('001'));
-				if (!$marc->field('001')) {
-					$marc->insert_fields_ordered(
-						MARC::Field->new( '001', $record->id )
-					);
-				}
-				$marc->delete_field($_) for ($marc->field('852')); # remove any legacy 852s
-				foreach my $cn (keys %$bib_holdings) {
-					foreach my $cp (@{$bib_holdings->{$cn}->{'copies'}}) {
-						$marc->insert_fields_ordered(
-							MARC::Field->new(
-								'852', '4', '',
-								a => $cp->{'location'},
-								b => $bib_holdings->{$cn}->{'owning_lib'},
-								c => $cn,
-								d => $cp->{'circlib'},
-								g => $cp->{'barcode'},
-								n => $cp->{'status'},
-							)
-						);
-					}
-				}
-
-				# Ensure the data is encoded as UTF8 before we hand it off
-				$marcxml = encode_utf8($marc->as_xml_record());
-				$marcxml =~ s/^<\?xml version="1.0" encoding="UTF-8"\?>//o;
-
-			}
-			$resp->addRecord(
-				SRU::Response::Record->new(
-					recordSchema    => 'info:srw/schema/1/marcxml-v1.1',
-					recordData => $marcxml,
-					recordPosition => ++$offset
-				)
-			);
-		}
-
-		$resp->numberOfRecords($recs->{count});
-
-	} elsif ( $resp->type eq 'explain' ) {
-		return_sru_explain($cgi, $req, $resp, \$ex_doc,
-			\%OpenILS::WWW::SuperCat::nested_qualifier_map,
-			\%OpenILS::WWW::SuperCat::qualifier_ids
-		);
-
-		$resp->record(
-			SRU::Response::Record->new(
-				recordSchema	=> 'info:srw/cql-context-set/2/zeerex-1.1',
-				recordData		=> $ex_doc
-			)
-		);
-	}
-
-	print $cgi->header( -type => 'application/xml' );
-	print $U->entityize($resp->asXML) . "\n";
-	return Apache2::Const::OK;
+         my $recs = $search->request(
+            'open-ils.search.biblio.multiclass.query' => {offset => $offset, limit => $limit} => $search_string => 1
+        )->gather(1);
+
+        my $bre = $supercat->request( 'open-ils.supercat.record.object.retrieve' => [ map { $_->[0] } @{$recs->{ids}} ] )->gather(1);
+
+        foreach my $record (@$bre) {
+            my $marcxml = $record->marc;
+            # Make the beast conform to a VDX-supported format
+            # See http://vdxipedia.oclc.org/index.php/Holdings_Parsing
+            # Trying to implement LIBSOL_852_A format; so much for standards
+            if ($holdings) {
+                my $bib_holdings = $supercat->request('open-ils.supercat.record.basic_holdings.retrieve', $record->id, $shortname || '-')->gather(1);
+                my $marc = MARC::Record->new_from_xml($marcxml, 'UTF8', 'XML');
+
+                # Force record leader to 'a' as our data is always UTF8
+                # Avoids marc8_to_utf8 from being invoked with horrible results
+                # on the off-chance the record leader isn't correct
+                my $ldr = $marc->leader;
+                substr($ldr, 9, 1, 'a');
+                $marc->leader($ldr);
+
+                # Expects the record ID in the 001
+                $marc->delete_field($_) for ($marc->field('001'));
+                if (!$marc->field('001')) {
+                    $marc->insert_fields_ordered(
+                        MARC::Field->new( '001', $record->id )
+                    );
+                }
+                $marc->delete_field($_) for ($marc->field('852')); # remove any legacy 852s
+                foreach my $cn (keys %$bib_holdings) {
+                    foreach my $cp (@{$bib_holdings->{$cn}->{'copies'}}) {
+                        $marc->insert_fields_ordered(
+                            MARC::Field->new(
+                                '852', '4', '',
+                                a => $cp->{'location'},
+                                b => $bib_holdings->{$cn}->{'owning_lib'},
+                                c => $cn,
+                                d => $cp->{'circlib'},
+                                g => $cp->{'barcode'},
+                                n => $cp->{'status'},
+                            )
+                        );
+                    }
+                }
+
+                # Ensure the data is encoded as UTF8 before we hand it off
+                $marcxml = encode_utf8($marc->as_xml_record());
+                $marcxml =~ s/^<\?xml version="1.0" encoding="UTF-8"\?>//o;
+
+            }
+            $resp->addRecord(
+                SRU::Response::Record->new(
+                    recordSchema    => 'info:srw/schema/1/marcxml-v1.1',
+                    recordData => $marcxml,
+                    recordPosition => ++$offset
+                )
+            );
+        }
+
+        $resp->numberOfRecords($recs->{count});
+
+    } elsif ( $resp->type eq 'explain' ) {
+        return_sru_explain($cgi, $req, $resp, \$ex_doc,
+            \%OpenILS::WWW::SuperCat::nested_qualifier_map,
+            \%OpenILS::WWW::SuperCat::qualifier_ids
+        );
+
+        $resp->record(
+            SRU::Response::Record->new(
+                recordSchema    => 'info:srw/cql-context-set/2/zeerex-1.1',
+                recordData        => $ex_doc
+            )
+        );
+    }
+
+    print $cgi->header( -type => 'application/xml' );
+    print $U->entityize($resp->asXML) . "\n";
+    return Apache2::Const::OK;
 }
 
 
@@ -2189,24 +2189,24 @@ Returns an aou object for a given actor.org_unit shortname or ID.
 =cut
 
 sub get_ou {
-	my $org = shift || '-';
-	my $org_unit;
-
-	if ($org eq '-') {
-	 	$org_unit = $actor->request(
-			'open-ils.actor.org_unit_list.search' => parent_ou => undef
-		)->gather(1);
-	} elsif ($org !~ /^\d+$/o) {
-	 	$org_unit = $actor->request(
-			'open-ils.actor.org_unit_list.search' => shortname => uc($org)
-		)->gather(1);
-	} else {
-	 	$org_unit = $actor->request(
-			'open-ils.actor.org_unit_list.search' => id => $org
-		)->gather(1);
-	}
-
-	return $org_unit;
+    my $org = shift || '-';
+    my $org_unit;
+
+    if ($org eq '-') {
+         $org_unit = $actor->request(
+            'open-ils.actor.org_unit_list.search' => parent_ou => undef
+        )->gather(1);
+    } elsif ($org !~ /^\d+$/o) {
+         $org_unit = $actor->request(
+            'open-ils.actor.org_unit_list.search' => shortname => uc($org)
+        )->gather(1);
+    } else {
+         $org_unit = $actor->request(
+            'open-ils.actor.org_unit_list.search' => id => $org
+        )->gather(1);
+    }
+
+    return $org_unit;
 }
 
 1;

-----------------------------------------------------------------------


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list