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 @@