diff --git a/.reek.yml b/.reek.yml
index e62891c29..513801373 100644
--- a/.reek.yml
+++ b/.reek.yml
@@ -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
diff --git a/Gemfile b/Gemfile
index 0b9793c0a..da0a24a82 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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'
diff --git a/Gemfile.lock b/Gemfile.lock
index 85cd7bc0e..dfb086dbe 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
@@ -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)
@@ -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)
@@ -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)
@@ -743,7 +757,9 @@ DEPENDENCIES
capistrano-passenger
capistrano-rails
capybara
+ citeproc-ruby
coveralls_reborn
+ csl-styles
datadog
devise
devise-guests
diff --git a/app/components/orangelight/document/citation_component.html.erb b/app/components/orangelight/document/citation_component.html.erb
new file mode 100644
index 000000000..7af3dd7e2
--- /dev/null
+++ b/app/components/orangelight/document/citation_component.html.erb
@@ -0,0 +1,11 @@
+
+
<%= title %>
+
+ <% @formats.each do |i18n_key, citation_method| %>
+ <%= t(i18n_key) %>
+ <%= @document.send(citation_method).html_safe %>
+ <% unless @formats.keys.last === i18n_key %>
+
+ <% end %>
+ <% end %>
+
diff --git a/app/components/orangelight/document/citation_component.rb b/app/components/orangelight/document/citation_component.rb
new file mode 100644
index 000000000..32fc490a4
--- /dev/null
+++ b/app/components/orangelight/document/citation_component.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class Orangelight::Document::CitationComponent < Blacklight::Document::CitationComponent
+ DEFAULT_FORMATS = {
+ 'blacklight.citation.mla': :export_as_mla,
+ 'blacklight.citation.apa': :export_as_apa,
+ 'blacklight.citation.chicago': :export_as_chicago
+ }.freeze
+
+ def initialize(document:, formats: DEFAULT_FORMATS)
+ super
+ end
+end
diff --git a/app/models/concerns/blacklight/document/apa.rb b/app/models/concerns/blacklight/document/apa.rb
new file mode 100644
index 000000000..16657b8a5
--- /dev/null
+++ b/app/models/concerns/blacklight/document/apa.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# Creates an html APA citation for non-Marc records
+module Blacklight::Document::Apa
+ 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
diff --git a/app/models/concerns/blacklight/document/chicago.rb b/app/models/concerns/blacklight/document/chicago.rb
new file mode 100644
index 000000000..4d56f4b42
--- /dev/null
+++ b/app/models/concerns/blacklight/document/chicago.rb
@@ -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
diff --git a/app/models/concerns/blacklight/document/cite_proc.rb b/app/models/concerns/blacklight/document/cite_proc.rb
new file mode 100644
index 000000000..ee4d88954
--- /dev/null
+++ b/app/models/concerns/blacklight/document/cite_proc.rb
@@ -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
diff --git a/app/models/concerns/blacklight/document/mla.rb b/app/models/concerns/blacklight/document/mla.rb
new file mode 100644
index 000000000..015e97187
--- /dev/null
+++ b/app/models/concerns/blacklight/document/mla.rb
@@ -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
diff --git a/app/models/solr_document.rb b/app/models/solr_document.rb
index 491074d63..3f95248c3 100644
--- a/app/models/solr_document.rb
+++ b/app/models/solr_document.rb
@@ -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] ||= []
diff --git a/app/views/catalog/_citation.html.erb b/app/views/catalog/_citation.html.erb
new file mode 100644
index 000000000..24e761ec9
--- /dev/null
+++ b/app/views/catalog/_citation.html.erb
@@ -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 %>
diff --git a/app/views/catalog/_previous_next_doc.html.erb b/app/views/catalog/_previous_next_doc.html.erb
index 291f42675..def2d903b 100644
--- a/app/views/catalog/_previous_next_doc.html.erb
+++ b/app/views/catalog/_previous_next_doc.html.erb
@@ -18,7 +18,7 @@