Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add citations for SCSB items #4427

Merged
merged 6 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@ detectors:
- Requests::RequestMailer#service_error_email
- Bookmark#self.destroy_without_solr_documents
- Bookmark#self.update_to_alma_ids
- Blacklight::Document::CiteProc#properties
- Blacklight::Document::DublinCore#export_as_oai_dc_xml
- Blacklight::Document::DublinCore#export_as_rdf_dc
- Blacklight::Document::Email#add_holdings_text
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ gem 'bootstrap', '~> 4.6'
# In the Capistrano documentation, it has these limited to the development group, and `require: false``
gem 'capistrano', '~> 3.4'
gem 'capistrano-passenger'
# support for non-marc citations (e.g. SCSB records)
gem 'citeproc-ruby'
gem 'csl-styles'
# Authentication and authorization
gem 'devise'
gem 'devise-guests'
Expand Down
16 changes: 16 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@ GEM
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
chronic (0.10.2)
citeproc (1.0.10)
namae (~> 1.0)
citeproc-ruby (2.1.0)
citeproc (~> 1.0, >= 1.0.9)
csl (~> 2.0)
observer (< 1.0)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
Expand All @@ -193,6 +199,11 @@ GEM
bigdecimal
rexml
crass (1.0.6)
csl (2.0.0)
namae (~> 1.0)
rexml
csl-styles (2.0.1)
csl (~> 2.0)
csv (3.3.0)
datadog (2.3.0)
debase-ruby_core_source (= 3.3.1)
Expand Down Expand Up @@ -389,6 +400,8 @@ GEM
msgpack (1.7.3)
multi_xml (0.6.0)
mutex_m (0.2.0)
namae (1.2.0)
racc (~> 1.7)
net-http (0.4.1)
uri
net-imap (0.4.15)
Expand All @@ -414,6 +427,7 @@ GEM
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4)
observer (0.1.2)
omniauth (2.1.2)
hashie (>= 3.4.6)
rack (>= 2.2.3)
Expand Down Expand Up @@ -743,7 +757,9 @@ DEPENDENCIES
capistrano-passenger
capistrano-rails
capybara
citeproc-ruby
coveralls_reborn
csl-styles
datadog
devise
devise-guests
Expand Down
11 changes: 11 additions & 0 deletions app/components/orangelight/document/citation_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div>
<h1 class="modal-title"><%= title %></h1>

<% @formats.each do |i18n_key, citation_method| %>
<h2><%= t(i18n_key) %></h2>
<%= @document.send(citation_method).html_safe %>
<% unless @formats.keys.last === i18n_key %>
<br/><br/>
<% end %>
<% end %>
</div>
13 changes: 13 additions & 0 deletions app/components/orangelight/document/citation_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

class Orangelight::Document::CitationComponent < Blacklight::Document::CitationComponent
DEFAULT_FORMATS = {
'blacklight.citation.mla': :export_as_mla,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a custom routine for each citation form we want to support, correct? I like that this approach would route all types of records through the same routines. We definitely don't want some records to go through blacklight-marc and others to go through citeproc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the next step is to add the other citation formats. Right now if it's an Alma record it does use the Marc version, but we could change that once we've gotten the CiteProc code more refined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ticket to route all types of records through the same routines - #4428

'blacklight.citation.apa': :export_as_apa,
'blacklight.citation.chicago': :export_as_chicago
}.freeze

def initialize(document:, formats: DEFAULT_FORMATS)
super
end
end
21 changes: 21 additions & 0 deletions app/models/concerns/blacklight/document/apa.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

# Creates an html APA citation for non-Marc records
module Blacklight::Document::Apa
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this structure with the 3 modules for the 3 citation styles

def self.extended(document)
Blacklight::Document::Apa.register_export_formats(document)
end

def self.register_export_formats(document)
document.will_export_as(:apa, 'text/html')
end

def export_as_apa
return export_as_apa_citation_txt if alma?

cp = CiteProc::Processor.new style: 'apa', format: 'html'
item = CiteProc::Item.new(properties)
cp.import(item)
cp.render(:bibliography, id:).first
end
end
21 changes: 21 additions & 0 deletions app/models/concerns/blacklight/document/chicago.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

# Creates an html Chicago citation for non-Marc records
module Blacklight::Document::Chicago
def self.extended(document)
Blacklight::Document::Chicago.register_export_formats(document)
end

def self.register_export_formats(document)
document.will_export_as(:chicago, 'text/html')
end

def export_as_chicago
return export_as_chicago_citation_txt if alma?

cp = CiteProc::Processor.new style: 'chicago-author-date', format: 'html'
item = CiteProc::Item.new(properties)
cp.import(item)
cp.render(:bibliography, id:).first
end
end
62 changes: 62 additions & 0 deletions app/models/concerns/blacklight/document/cite_proc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

## Adds the methods needed for CiteProc citations,
# Including MLA, APA, and Chicago
module Blacklight::Document::CiteProc
private

def properties
props = {}
props[:id] = id
props[:edition] = cite_proc_edition if cite_proc_edition
props[:type] = cite_proc_type if cite_proc_type
props[:author] = cite_proc_author if cite_proc_author
props[:title] = cite_proc_title if cite_proc_title
props[:publisher] = cite_proc_publisher if cite_proc_publisher
props[:'publisher-place'] = cite_proc_publisher_place if cite_proc_publisher_place
props[:issued] = cite_proc_issued if cite_proc_issued
props
end

def cite_proc_type
self[:format]&.first&.downcase
end

def cite_proc_author
@cite_proc_author ||= begin
family, given = citation_fields_from_solr[:author_citation_display]&.first&.split(', ')
CiteProc::Name.new(family:, given:) if family || given
end
end

def cite_proc_edition
@cite_proc_edition ||= begin
str = citation_fields_from_solr[:edition_display]&.first
str&.dup&.sub!(/[[:punct:]]?$/, '')
end
end

def cite_proc_title
@cite_proc_title ||= citation_fields_from_solr[:title_citation_display]&.first
end

def cite_proc_publisher
@cite_proc_publisher ||= citation_fields_from_solr[:pub_citation_display]&.first&.split(': ').try(:[], 1)
end

def cite_proc_publisher_place
@cite_proc_publisher_place ||= citation_fields_from_solr[:pub_citation_display]&.first&.split(': ').try(:[], 0)
end

def cite_proc_issued
@cite_proc_issued ||= citation_fields_from_solr[:pub_date_start_sort]
end

def citation_fields_from_solr
@citation_fields_from_solr ||= begin
params = { q: "id:#{RSolr.solr_escape(id)}", fl: "author_citation_display, title_citation_display, pub_citation_display, number_of_pages_citation_display, pub_date_start_sort, edition_display" }
solr_response = Blacklight.default_index.connection.get('select', params:)
solr_response["response"]["docs"].first.with_indifferent_access
end
end
end
21 changes: 21 additions & 0 deletions app/models/concerns/blacklight/document/mla.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

# Creates an html MLA citation for non-Marc records
module Blacklight::Document::Mla
def self.extended(document)
Blacklight::Document::Mla.register_export_formats(document)
end

def self.register_export_formats(document)
document.will_export_as(:mla, 'text/html')
end

def export_as_mla
return export_as_mla_citation_txt if alma?

cp = CiteProc::Processor.new style: 'modern-language-association', format: 'html'
item = CiteProc::Item.new(properties)
cp.import(item)
cp.render(:bibliography, id:).first
end
end
13 changes: 13 additions & 0 deletions app/models/solr_document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ class SolrDocument
## Adds JSON-LD
use_extension(Blacklight::Document::JsonLd)

## Adds the methods needed for CiteProc citations,
# Including MLA, APA, and Chicago
use_extension(Blacklight::Document::CiteProc)

## Adds MLA html
use_extension(Blacklight::Document::Mla)

# Adds APA html
use_extension(Blacklight::Document::Apa)

# Adds Chicago html
use_extension(Blacklight::Document::Chicago)

def identifier_data
values = identifiers.each_with_object({}) do |identifier, hsh|
hsh[identifier.data_key.to_sym] ||= []
Expand Down
4 changes: 4 additions & 0 deletions app/views/catalog/_citation.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<%= render Blacklight::System::ModalComponent.new do |component| %>
<% component.with_title { t('blacklight.tools.citation') } %>
<%= render Orangelight::Document::CitationComponent.with_collection(@documents) if @documents.present? %>
<% end %>
2 changes: 1 addition & 1 deletion app/views/catalog/_previous_next_doc.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</div>
<div class="search-widgets col pr-4">
<ul class="navbar navbar-nav pull-right">
<% if @document.alma_record? %>
<% if @document.alma_record? || @document.scsb_record? %>
<li>
<%= render_cite_link(citation_solr_document_path(id: @document)) %>
</li>
Expand Down
18 changes: 18 additions & 0 deletions spec/components/orangelight/document/citation_component_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Orangelight::Document::CitationComponent, type: :component, citation: true do
let(:document) { SolrDocument.new(properties) }
let(:properties) do
{
id: 'SCSB-2635660',
author_citation_display: ["Saer, Juan José"]
}
end
let(:component) { described_class.new(document:) }

it 'can be rendered' do
expect(render_inline(component).to_s).to include('Saer')
end
end
46 changes: 33 additions & 13 deletions spec/features/citation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,43 @@

require 'rails_helper'

describe 'citation' do
describe 'citation', citation: true do
before do
allow_any_instance_of(CatalogController).to receive(:agent_is_crawler?).and_return(false)
end
it 'will render successfully even if there is not a subfield a' do
stub_request(:get, "#{Requests.config['bibdata_base']}/bibliographic/9979948663506421")
.with(
headers: {
'Accept' => '*/*',
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
'User-Agent' => 'Faraday v0.17.4'
}
context 'with an alma marcxml record' do
let(:bibid) { '9979948663506421' }
let(:marc_xml) { File.read(File.join(File.dirname(__FILE__), '..', 'fixtures', 'bibdata', "#{bibid}.xml")) }

before do
stub_request(:get, "#{Requests.config['bibdata_base']}/bibliographic/#{bibid}").to_return(
status: 200,
body: marc_xml
)
.to_return(status: 200, body: "", headers: {})
end
it 'will render successfully even if there is not a subfield a' do
visit '/catalog/9979948663506421/citation'
expect(current_url).to include '/catalog/9979948663506421/citation'
expect(page.status_code).to eq 200
end

it 'renders the citation' do
visit '/catalog/9979948663506421/citation'
expect(page.body).to include('Henderson, W. J, et al.')
end
end

context 'with a scsb record' do
let(:bibid) { 'SCSB-2635660' }
before do
stub_request(:get, "#{Requests.config['bibdata_base']}/bibliographic/#{bibid}")
.to_return(status: 404)
end

visit '/catalog/9979948663506421/citation'
expect(current_url).to include '/catalog/9979948663506421/citation'
expect(page.status_code).to eq 200
it 'renders the citation' do
visit "/catalog/#{bibid}/citation"
expect(current_url).to include("/catalog/#{bibid}/citation")
expect(page.body).to include('Juan José. <i>El Entenado</i>. 1A edición, Destino, 1988.')
end
end
end
Loading