iiif_print 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +2 -0
- data/.env +5 -0
- data/.fcrepo_wrapper +4 -0
- data/.github/release.yml +20 -0
- data/.github/workflows/branches.yml +24 -0
- data/.github/workflows/build-lint-test-action.yaml +33 -0
- data/.github/workflows/release_labels.yml +25 -0
- data/.gitignore +52 -0
- data/.rubocop.yml +177 -0
- data/.solr_wrapper +8 -0
- data/.travis.yml +49 -0
- data/CONTRIBUTING.md +181 -0
- data/Dockerfile +15 -0
- data/Gemfile +52 -0
- data/LICENSE +203 -0
- data/README.md +203 -0
- data/Rakefile +38 -0
- data/app/actors/iiif_print/actors/file_set_actor_decorator.rb +56 -0
- data/app/assets/config/iiif_print_manifest.js +2 -0
- data/app/assets/images/iiif_print/.keep +0 -0
- data/app/assets/javascripts/iiif_print/autocomplete_fix.js +33 -0
- data/app/assets/javascripts/iiif_print/ocr_search.js.erb +6 -0
- data/app/assets/javascripts/iiif_print.js +3 -0
- data/app/assets/stylesheets/iiif_print/_iiif_print.scss +4 -0
- data/app/assets/stylesheets/iiif_print/_issue_search.scss +13 -0
- data/app/assets/stylesheets/iiif_print/_issues_calendar.scss +18 -0
- data/app/assets/stylesheets/iiif_print/_newspapers_search.scss +38 -0
- data/app/assets/stylesheets/iiif_print/_search_results.scss +6 -0
- data/app/helpers/hyrax/iiif_helper.rb +22 -0
- data/app/helpers/iiif_print/application_helper.rb +5 -0
- data/app/helpers/iiif_print_helper.rb +64 -0
- data/app/indexers/concerns/iiif_print/child_indexer.rb +34 -0
- data/app/indexers/concerns/iiif_print/file_set_indexer.rb +29 -0
- data/app/mailers/iiif_print/application_mailer.rb +8 -0
- data/app/models/concerns/iiif_print/set_child_flag.rb +29 -0
- data/app/models/concerns/iiif_print/solr/document.rb +47 -0
- data/app/models/iiif_print/application_record.rb +6 -0
- data/app/models/iiif_print/derivative_attachment.rb +8 -0
- data/app/models/iiif_print/iiif_search_response_decorator.rb +17 -0
- data/app/models/iiif_print/ingest_file_relation.rb +14 -0
- data/app/models/iiif_print/pending_relationship.rb +7 -0
- data/app/presenters/iiif_print/iiif_manifest_presenter_behavior.rb +10 -0
- data/app/presenters/iiif_print/iiif_manifest_presenter_factory_behavior.rb +33 -0
- data/app/presenters/iiif_print/work_show_presenter_decorator.rb +29 -0
- data/app/renderers/hyrax/renderers/faceted_attribute_renderer_decorator.rb +18 -0
- data/app/search_builders/concerns/iiif_print/exclude_models.rb +17 -0
- data/app/search_builders/concerns/iiif_print/highlight_search_params.rb +14 -0
- data/app/services/iiif_print/manifest_builder_service_behavior.rb +97 -0
- data/app/services/iiif_print/pluggable_derivative_service.rb +120 -0
- data/app/views/catalog/_snippets_more.html.erb +16 -0
- data/app/views/hyrax/base/_representative_media.html.erb +9 -0
- data/app/views/hyrax/base/iiif_viewers/_universal_viewer.html.erb +8 -0
- data/app/views/hyrax/file_sets/_actions.html.erb +45 -0
- data/bin/rails +13 -0
- data/config/fcrepo_wrapper_test.yml +5 -0
- data/config/initializers/assets.rb +2 -0
- data/config/locales/iiif_print.de.yml +148 -0
- data/config/locales/iiif_print.en.yml +119 -0
- data/config/locales/iiif_print.es.yml +148 -0
- data/config/locales/iiif_print.fr.yml +149 -0
- data/config/locales/iiif_print.it.yml +142 -0
- data/config/locales/iiif_print.pt-BR.yml +148 -0
- data/config/locales/iiif_print.zh.yml +142 -0
- data/config/solr_wrapper_test.yml +9 -0
- data/config/test-fixture/solr-config/_rest_managed.json +3 -0
- data/config/test-fixture/solr-config/admin-extra.html +31 -0
- data/config/test-fixture/solr-config/elevate.xml +36 -0
- data/config/test-fixture/solr-config/mapping-ISOLatin1Accent.txt +246 -0
- data/config/test-fixture/solr-config/protwords.txt +21 -0
- data/config/test-fixture/solr-config/schema.xml +366 -0
- data/config/test-fixture/solr-config/scripts.conf +24 -0
- data/config/test-fixture/solr-config/solrconfig.xml +322 -0
- data/config/test-fixture/solr-config/spellings.txt +2 -0
- data/config/test-fixture/solr-config/stopwords.txt +58 -0
- data/config/test-fixture/solr-config/stopwords_en.txt +58 -0
- data/config/test-fixture/solr-config/synonyms.txt +31 -0
- data/config/test-fixture/solr-config/xslt/example.xsl +132 -0
- data/config/test-fixture/solr-config/xslt/example_atom.xsl +67 -0
- data/config/test-fixture/solr-config/xslt/example_rss.xsl +66 -0
- data/config/test-fixture/solr-config/xslt/luke.xsl +337 -0
- data/config/vendor/fits.xml +55 -0
- data/config/vendor/imagemagick-6-policy.xml +76 -0
- data/db/migrate/20181214181358_create_iiif_print_derivative_attachments.rb +12 -0
- data/db/migrate/20190107165909_create_iiif_print_ingest_file_relations.rb +11 -0
- data/db/migrate/20230109000000_create_iiif_print_pending_relationships.rb +11 -0
- data/docker-compose.yml +129 -0
- data/iiif_print.gemspec +43 -0
- data/lib/generators/iiif_print/assets_generator.rb +29 -0
- data/lib/generators/iiif_print/catalog_controller_generator.rb +32 -0
- data/lib/generators/iiif_print/install_generator.rb +52 -0
- data/lib/generators/iiif_print/templates/config/initializers/iiif_print.rb +22 -0
- data/lib/generators/iiif_print/templates/iiif_print.scss +1 -0
- data/lib/iiif_print/base_derivative_service.rb +113 -0
- data/lib/iiif_print/blacklight_iiif_search/annotation_decorator.rb +84 -0
- data/lib/iiif_print/catalog_search_builder.rb +31 -0
- data/lib/iiif_print/configuration.rb +99 -0
- data/lib/iiif_print/data/fileset_helper.rb +25 -0
- data/lib/iiif_print/data/path_helper.rb +40 -0
- data/lib/iiif_print/data/work_derivatives.rb +323 -0
- data/lib/iiif_print/data/work_file.rb +92 -0
- data/lib/iiif_print/data/work_files.rb +199 -0
- data/lib/iiif_print/data.rb +35 -0
- data/lib/iiif_print/engine.rb +77 -0
- data/lib/iiif_print/errors.rb +9 -0
- data/lib/iiif_print/image_tool.rb +119 -0
- data/lib/iiif_print/jobs/application_job.rb +8 -0
- data/lib/iiif_print/jobs/child_works_from_pdf_job.rb +107 -0
- data/lib/iiif_print/jobs/create_relationships_job.rb +78 -0
- data/lib/iiif_print/jp2_derivative_service.rb +118 -0
- data/lib/iiif_print/jp2_image_metadata.rb +81 -0
- data/lib/iiif_print/lineage_service.rb +41 -0
- data/lib/iiif_print/metadata.rb +125 -0
- data/lib/iiif_print/pdf_derivative_service.rb +42 -0
- data/lib/iiif_print/split_pdfs/child_work_creation_from_pdf_service.rb +75 -0
- data/lib/iiif_print/split_pdfs/pages_into_images_service.rb +130 -0
- data/lib/iiif_print/split_pdfs/pdf_image_extraction_service.rb +85 -0
- data/lib/iiif_print/text_extraction/alto_reader.rb +123 -0
- data/lib/iiif_print/text_extraction/hocr_reader.rb +172 -0
- data/lib/iiif_print/text_extraction/page_ocr.rb +87 -0
- data/lib/iiif_print/text_extraction/render_alto.rb +84 -0
- data/lib/iiif_print/text_extraction/word_coords_builder.rb +38 -0
- data/lib/iiif_print/text_extraction.rb +11 -0
- data/lib/iiif_print/text_extraction_derivative_service.rb +47 -0
- data/lib/iiif_print/text_formats_from_alto_service.rb +77 -0
- data/lib/iiif_print/tiff_derivative_service.rb +50 -0
- data/lib/iiif_print/version.rb +3 -0
- data/lib/iiif_print/works_controller_behavior.rb +9 -0
- data/lib/iiif_print.rb +136 -0
- data/lib/tasks/set_child_works.rake +22 -0
- data/spec/.keep.txt +1 -0
- data/spec/factories/ability.rb +6 -0
- data/spec/factories/newspaper_issue.rb +7 -0
- data/spec/factories/newspaper_page.rb +7 -0
- data/spec/factories/newspaper_page_solr_document.rb +12 -0
- data/spec/factories/newspaper_title.rb +8 -0
- data/spec/factories/uploaded_pdf_file.rb +9 -0
- data/spec/factories/uploaded_txt_file.rb +9 -0
- data/spec/factories/user.rb +13 -0
- data/spec/fixtures/files/4.1.07.jp2 +0 -0
- data/spec/fixtures/files/4.1.07.tiff +0 -0
- data/spec/fixtures/files/README.md +7 -0
- data/spec/fixtures/files/alto-2-0.xsd +714 -0
- data/spec/fixtures/files/broken-truncated.pdf +0 -0
- data/spec/fixtures/files/credits.md +16 -0
- data/spec/fixtures/files/lowres-gray-via-ndnp-sample.tiff +0 -0
- data/spec/fixtures/files/minimal-1-page.pdf +0 -0
- data/spec/fixtures/files/minimal-2-page.pdf +0 -0
- data/spec/fixtures/files/minimal-alto.xml +31 -0
- data/spec/fixtures/files/ndnp-alto-sample.xml +24 -0
- data/spec/fixtures/files/ndnp-sample1-json.json +1 -0
- data/spec/fixtures/files/ndnp-sample1-txt.txt +1 -0
- data/spec/fixtures/files/ndnp-sample1.pdf +0 -0
- data/spec/fixtures/files/ocr_alto.xml +202 -0
- data/spec/fixtures/files/ocr_alto_scaled_4pts_per_px.xml +202 -0
- data/spec/fixtures/files/ocr_color.tiff +0 -0
- data/spec/fixtures/files/ocr_gray.jp2 +0 -0
- data/spec/fixtures/files/ocr_gray.tiff +0 -0
- data/spec/fixtures/files/ocr_mono.tiff +0 -0
- data/spec/fixtures/files/ocr_mono_text_hocr.html +78 -0
- data/spec/fixtures/files/page1.tiff +0 -0
- data/spec/fixtures/files/sample-4page-issue.pdf +0 -0
- data/spec/fixtures/files/sample-color-newsletter.pdf +0 -0
- data/spec/fixtures/files/thumbnail.jpg +0 -0
- data/spec/helpers/hyrax/iiif_helper_spec.rb +65 -0
- data/spec/helpers/iiif_print_helper_spec.rb +43 -0
- data/spec/iiif_print/base_derivative_service_spec.rb +11 -0
- data/spec/iiif_print/blacklight_iiif_search/annotation_decorator_spec.rb +51 -0
- data/spec/iiif_print/catalog_search_builder_spec.rb +60 -0
- data/spec/iiif_print/configuration_spec.rb +67 -0
- data/spec/iiif_print/data/work_derivatives_spec.rb +245 -0
- data/spec/iiif_print/data/work_file_spec.rb +99 -0
- data/spec/iiif_print/data/work_files_spec.rb +237 -0
- data/spec/iiif_print/image_tool_spec.rb +109 -0
- data/spec/iiif_print/jobs/child_works_from_pdf_job_spec.rb +30 -0
- data/spec/iiif_print/jobs/create_relationships_job_spec.rb +17 -0
- data/spec/iiif_print/jp2_image_metadata_spec.rb +37 -0
- data/spec/iiif_print/lineage_service_spec.rb +13 -0
- data/spec/iiif_print/metadata_spec.rb +115 -0
- data/spec/iiif_print/split_pdfs/pages_into_images_service_spec.rb +6 -0
- data/spec/iiif_print/text_extraction/alto_reader_spec.rb +49 -0
- data/spec/iiif_print/text_extraction/hocr_reader_spec.rb +45 -0
- data/spec/iiif_print/text_extraction/page_ocr_spec.rb +84 -0
- data/spec/iiif_print/text_extraction/render_alto_spec.rb +54 -0
- data/spec/iiif_print/text_extraction/word_coords_builder_spec.rb +44 -0
- data/spec/iiif_print_spec.rb +51 -0
- data/spec/misc_shared.rb +111 -0
- data/spec/models/iiif_print/derivative_attachment_spec.rb +37 -0
- data/spec/models/iiif_print/ingest_file_relation_spec.rb +56 -0
- data/spec/models/solr_document_spec.rb +14 -0
- data/spec/presenters/iiif_print/iiif_manifest_presenter_behavior_spec.rb +19 -0
- data/spec/presenters/iiif_print/iiif_manifest_presenter_factory_behavior_spec.rb +49 -0
- data/spec/services/iiif_print/jp2_derivative_service_spec.rb +59 -0
- data/spec/services/iiif_print/pdf_derivative_service_spec.rb +66 -0
- data/spec/services/iiif_print/pluggable_derivative_service_spec.rb +178 -0
- data/spec/services/iiif_print/text_extraction_derivative_service_spec.rb +82 -0
- data/spec/services/iiif_print/text_formats_from_alto_service_spec.rb +127 -0
- data/spec/services/iiif_print/tiff_derivative_service_spec.rb +65 -0
- data/spec/spec_helper.rb +181 -0
- data/spec/support/controller_level_helpers.rb +28 -0
- data/spec/support/iiif_print_models.rb +127 -0
- data/spec/test_app_templates/blacklight.yml +9 -0
- data/spec/test_app_templates/fedora.yml +15 -0
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +40 -0
- data/spec/test_app_templates/redis.yml +9 -0
- data/spec/test_app_templates/solr/conf/schema.xml +362 -0
- data/spec/test_app_templates/solr/conf/solrconfig.xml +322 -0
- data/spec/test_app_templates/solr.yml +7 -0
- data/tasks/iiif_print_dev.rake +34 -0
- data/tmp/.keep +0 -0
- metadata +605 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
module IiifPrintHelper
|
2
|
+
##
|
3
|
+
# create link anchor to be read by UniversalViewer
|
4
|
+
# in order to show keyword search
|
5
|
+
# @param query_params_hash [Hash] current_search_session.query_params
|
6
|
+
# @return [String] or [nil] anchor
|
7
|
+
def iiif_search_anchor(query_params_hash)
|
8
|
+
query = search_query(query_params_hash)
|
9
|
+
return nil if query.blank?
|
10
|
+
"?h=#{query}"
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# get the query, which may be in a different object,
|
15
|
+
# depending if regular search or newspapers_search was run
|
16
|
+
# @param query_params_hash [Hash] current_search_session.query_params
|
17
|
+
# @return [String] or [nil] query
|
18
|
+
def search_query(query_params_hash)
|
19
|
+
query_params_hash[:q] || query_params_hash[:all_fields]
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# return the matching highlighted terms from Solr highlight field
|
24
|
+
#
|
25
|
+
# @param document [SolrDocument]
|
26
|
+
# @param hl_fl [String] the name of the Solr field with highlights
|
27
|
+
# @param hl_tag [String] the HTML element name used for marking highlights
|
28
|
+
# configured in Solr as hl.tag.pre value
|
29
|
+
# @return [String]
|
30
|
+
def highlight_matches(document, hl_fl, hl_tag)
|
31
|
+
hl_matches = []
|
32
|
+
# regex: find all chars between hl_tag, but NOT other <element>
|
33
|
+
regex = /<#{hl_tag}>[^<>]+<\/#{hl_tag}>/
|
34
|
+
hls = document.highlight_field(hl_fl)
|
35
|
+
return nil if hls.blank?
|
36
|
+
hls.each do |hl|
|
37
|
+
matches = hl.scan(regex)
|
38
|
+
matches.each do |match|
|
39
|
+
hl_matches << match.gsub(/<[\/]*#{hl_tag}>/, '').downcase
|
40
|
+
end
|
41
|
+
end
|
42
|
+
hl_matches.uniq.sort.join(' ')
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# print the ocr snippets. if more than one, separate with <br/>
|
47
|
+
#
|
48
|
+
# @param options [Hash] options hash provided by Blacklight
|
49
|
+
# @return [String] snippets HTML to be rendered
|
50
|
+
# rubocop:disable Rails/OutputSafety
|
51
|
+
def render_ocr_snippets(options = {})
|
52
|
+
snippets = options[:value]
|
53
|
+
snippets_content = [content_tag('div',
|
54
|
+
"... #{snippets.first} ...".html_safe,
|
55
|
+
class: 'ocr_snippet first_snippet')]
|
56
|
+
if snippets.length > 1
|
57
|
+
snippets_content << render(partial: 'catalog/snippets_more',
|
58
|
+
locals: { snippets: snippets.drop(1),
|
59
|
+
options: options })
|
60
|
+
end
|
61
|
+
snippets_content.join("\n").html_safe
|
62
|
+
end
|
63
|
+
# rubocop:enable Rails/OutputSafety
|
64
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IiifPrint
|
4
|
+
module ChildIndexer
|
5
|
+
##
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
# The goal of this method is to encapsulate the logic for what all we need for child
|
9
|
+
# relationships.
|
10
|
+
def self.decorate_work_types!
|
11
|
+
# TODO: This method is in the wrong location; says indexing but there's also the SetChildFlag
|
12
|
+
# consideration. Consider refactoring this stuff into a single nested module.
|
13
|
+
#
|
14
|
+
|
15
|
+
Hyrax.config.curation_concerns.each do |work_type|
|
16
|
+
work_type.send(:include, IiifPrint::SetChildFlag) unless work_type.included_modules.include?(IiifPrint::SetChildFlag)
|
17
|
+
indexer = work_type.indexer
|
18
|
+
unless indexer.respond_to?(:iiif_print_lineage_service)
|
19
|
+
indexer.prepend(self)
|
20
|
+
indexer.class_attribute(:iiif_print_lineage_service, default: IiifPrint::LineageService)
|
21
|
+
end
|
22
|
+
work_type::GeneratedResourceSchema.send(:include, IiifPrint::SetChildFlag)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate_solr_document
|
27
|
+
super.tap do |solr_doc|
|
28
|
+
solr_doc['is_child_bsi'] = object.is_child
|
29
|
+
solr_doc['is_page_of_ssim'] = iiif_print_lineage_service.ancestor_ids_for(object)
|
30
|
+
solr_doc['file_set_ids_ssim'] = iiif_print_lineage_service.descendent_file_set_ids_for(object)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IiifPrint
|
4
|
+
module FileSetIndexer
|
5
|
+
# Why `.decorate`? In my tests for Rails 5.2, I'm not able to use the prepended nor included
|
6
|
+
# blocks to assign a class_attribute when I "prepend" a module to the base class. This method
|
7
|
+
# allows me to handle that behavior.
|
8
|
+
#
|
9
|
+
# @param base [Class]
|
10
|
+
# @return [Class] the given base, now decorated in all of it's glory
|
11
|
+
def self.decorate(base)
|
12
|
+
base.prepend(self)
|
13
|
+
base.class_attribute :iiif_print_lineage_service, default: IiifPrint::LineageService
|
14
|
+
base
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_solr_document
|
18
|
+
super.tap do |solr_doc|
|
19
|
+
# only UV viewable images should have is_page_of, it is only used for iiif search
|
20
|
+
solr_doc['is_page_of_ssim'] = iiif_print_lineage_service.ancestor_ids_for(object) if object.mime_type&.match(/image/)
|
21
|
+
# index for full text search
|
22
|
+
text = IiifPrint::Data::WorkDerivatives.data(from: object, of_type: 'txt')
|
23
|
+
text = text.tr("\n", ' ').squeeze(' ')
|
24
|
+
solr_doc['all_text_timv'] = text
|
25
|
+
solr_doc['all_text_tsimv'] = text
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RDF
|
4
|
+
class CustomIsChildTerm < Vocabulary('http://id.loc.gov/vocabulary/identifiers/')
|
5
|
+
property 'is_child'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module IiifPrint
|
10
|
+
module SetChildFlag
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
included do
|
13
|
+
# Why the try? A work type's GeneratedResourceSchema goes through this path as well
|
14
|
+
# and does not have an #after_save resulting in a NoMethodError.
|
15
|
+
try(:after_save, :set_children)
|
16
|
+
property :is_child,
|
17
|
+
predicate: ::RDF::CustomIsChildTerm.is_child,
|
18
|
+
multiple: false do |index|
|
19
|
+
index.as :stored_searchable
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_children
|
24
|
+
ordered_works.each do |child_work|
|
25
|
+
child_work.update(is_child: true) unless child_work.is_child
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module IiifPrint::Solr::Document
|
2
|
+
# @note Why decorate? We want to avoid including this module via generator. And the generator
|
3
|
+
# previously did two things: 1) include `IiifPrint::Solr::Document` in `SolrDocument`; 2)
|
4
|
+
# add the `attribute :is_child` field to the SolrDocument. We can't rely on `included do`
|
5
|
+
# block to handle that.
|
6
|
+
#
|
7
|
+
# This method is responsible for configuring the SolrDocument for a Hyrax/Hyku application. It
|
8
|
+
# does three things:
|
9
|
+
#
|
10
|
+
# 1. Adds instance methods to the SolrDocument (see implementation below)
|
11
|
+
# 2. Adds the `is_child` attribute to the SolrDocument
|
12
|
+
# 3. Adds a class attribute (e.g. `iiif_print_solr_field_names`) to allow further customization.
|
13
|
+
#
|
14
|
+
# @note These `iiif_print_solr_field_names` came from the newspaper_works implementation and are
|
15
|
+
# carried forward without much consideration, except to say "Make it configurable!"
|
16
|
+
#
|
17
|
+
# @param base [Class<SolrDocument>]
|
18
|
+
# @return [Class<SolrDocument>]
|
19
|
+
def self.decorate(base)
|
20
|
+
base.prepend(self)
|
21
|
+
base.send(:attribute, :is_child, Hyrax::SolrDocument::Metadata::Solr::String, 'is_child_bsi')
|
22
|
+
|
23
|
+
# @note These properties came from the newspaper_works gem. They are configurable.
|
24
|
+
base.class_attribute :iiif_print_solr_field_names, default: %w[alternative_title genre
|
25
|
+
issn lccn oclcnum held_by text_direction
|
26
|
+
page_number section author photographer
|
27
|
+
volume issue_number geographic_coverage
|
28
|
+
extent publication_date height width
|
29
|
+
edition_number edition_name frequency preceded_by
|
30
|
+
succeeded_by]
|
31
|
+
base
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(method_name, *args, &block)
|
35
|
+
super unless iiif_print_solr_field_names.include? method_name.to_s
|
36
|
+
self[::ActiveFedora.index_field_mapper.solr_name(method_name.to_s)]
|
37
|
+
end
|
38
|
+
|
39
|
+
def respond_to_missing?(method_name, include_private = false)
|
40
|
+
iiif_print_solr_field_names.include?(method_name.to_s) || super
|
41
|
+
end
|
42
|
+
|
43
|
+
# TODO: consider configuring this field name; we use the magic field in lots of places.
|
44
|
+
def file_set_ids
|
45
|
+
self['file_set_ids_ssim']
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module IiifPrint
|
2
|
+
class DerivativeAttachment < ApplicationRecord
|
3
|
+
# We can store nil/optional fileset as interim value before fileset
|
4
|
+
# construction, but we require at minimum, path, destination_name
|
5
|
+
validates :path, presence: true
|
6
|
+
validates :destination_name, presence: true
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module IiifPrint
|
2
|
+
module IiifSearchResponseDecorator
|
3
|
+
# Enable the user to search for child metadata in the parent's UV
|
4
|
+
# @see https://github.com/scientist-softserv/louisville-hyku/commit/67467e5cf9fdb755f54419f17d3c24c87032d0af
|
5
|
+
def annotation_list
|
6
|
+
json_results = super
|
7
|
+
json_results&.[]('resources')&.each do |result_hit|
|
8
|
+
next if result_hit['resource'].present?
|
9
|
+
result_hit['resource'] = {
|
10
|
+
"@type": "cnt:ContentAsText",
|
11
|
+
"chars": "Metadata match, see sidebar for details"
|
12
|
+
}
|
13
|
+
end
|
14
|
+
json_results
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module IiifPrint
|
2
|
+
class IngestFileRelation < ApplicationRecord
|
3
|
+
validates :file_path, presence: true
|
4
|
+
validates :derivative_path, presence: true
|
5
|
+
|
6
|
+
# Query by file path for all derivatives, as de-duplicated array of
|
7
|
+
# derivative paths.
|
8
|
+
# @param path [String] Path to primary file
|
9
|
+
# @return [Array<String>] de-duplicated array of derivative paths.
|
10
|
+
def self.derivatives_for_file(path)
|
11
|
+
where(file_path: path).pluck(:derivative_path).uniq
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# mixin to provide URL for IIIF Content Search service
|
2
|
+
module IiifPrint
|
3
|
+
module IiifManifestPresenterBehavior
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
def search_service
|
7
|
+
Rails.application.routes.url_helpers.solr_document_iiif_search_url(id, host: hostname)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module IiifPrint
|
2
|
+
module IiifManifestPresenterFactoryBehavior
|
3
|
+
# This will override Hyrax::IiifManifestPresenter::Factory#build and introducing
|
4
|
+
# the expected behavior:
|
5
|
+
# - child work images show as canvases in the parent work manifest
|
6
|
+
# - child work images show in the uv on the parent show page
|
7
|
+
# - still create the manifest if the parent work has images attached but the child works do not
|
8
|
+
def build
|
9
|
+
ids.map do |id|
|
10
|
+
solr_doc = load_docs.find { |doc| doc.id == id }
|
11
|
+
next unless solr_doc
|
12
|
+
|
13
|
+
if solr_doc.file_set?
|
14
|
+
presenter_class.for(solr_doc)
|
15
|
+
elsif Hyrax.config.curation_concerns.include?(solr_doc.hydra_model)
|
16
|
+
# look up file set ids and loop through those
|
17
|
+
file_set_docs = load_file_set_docs(solr_doc.file_set_ids)
|
18
|
+
file_set_docs.map { |doc| presenter_class.for(doc) } if file_set_docs.length
|
19
|
+
end
|
20
|
+
end.flatten.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# still create the manifest if the parent work has images attached but the child works do not
|
26
|
+
def load_file_set_docs(file_set_ids)
|
27
|
+
return [] if file_set_ids.nil?
|
28
|
+
|
29
|
+
query("{!terms f=id}#{file_set_ids.join(',')}", rows: 1000)
|
30
|
+
.map { |res| ::SolrDocument.new(res) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IiifPrint
|
4
|
+
module WorkShowPresenterDecorator
|
5
|
+
delegate :file_set_ids, to: :solr_document
|
6
|
+
|
7
|
+
# OVERRIDE Hyrax 2.9.6 to remove check for representative_presenter.image? and allow
|
8
|
+
# a fallback to check for images on the child works
|
9
|
+
# @return [Boolean] render a IIIF viewer
|
10
|
+
def iiif_viewer?
|
11
|
+
parent_work_has_files? || child_work_has_files?
|
12
|
+
end
|
13
|
+
|
14
|
+
alias universal_viewer? iiif_viewer?
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def parent_work_has_files?
|
19
|
+
Hyrax.config.iiif_image_server? &&
|
20
|
+
representative_id.present? &&
|
21
|
+
representative_presenter.present? &&
|
22
|
+
members_include_viewable_image?
|
23
|
+
end
|
24
|
+
|
25
|
+
def child_work_has_files?
|
26
|
+
file_set_ids.present?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# OVERRIDE Hyrax 2.9.6 to give user ability to display child works form linked facets
|
4
|
+
# We did work to display only parent works by default, for this client
|
5
|
+
module Hyrax
|
6
|
+
module Renderers
|
7
|
+
module FacetedAttributeRendererDecorator
|
8
|
+
private
|
9
|
+
|
10
|
+
# OVERRIDE Hyrax 2.9.6 to give user ability to display child works form linked facets
|
11
|
+
def search_path(value)
|
12
|
+
path = super(value)
|
13
|
+
path += '&include_child_works=true' if options[:is_child_bsi] == true
|
14
|
+
path
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module IiifPrint
|
2
|
+
# hide Title, Container, and Issue objects if this is a keyword search
|
3
|
+
# can be added to default_processor_chain in a SearchBuilder class
|
4
|
+
module ExcludeModels
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def exclude_models(solr_parameters, config: IiifPrint.config)
|
8
|
+
return unless solr_parameters[:q] || solr_parameters[:all_fields]
|
9
|
+
|
10
|
+
solr_parameters[:fq] ||= []
|
11
|
+
key = config.excluded_model_name_solr_field_key
|
12
|
+
config.excluded_model_name_solr_field_values.each do |value|
|
13
|
+
solr_parameters[:fq] << "-#{key}:\"#{value}\""
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module IiifPrint
|
2
|
+
# add highlighting on _stored_ full text field if this is a keyword search
|
3
|
+
# can be added to default_processor_chain in a SearchBuilder class
|
4
|
+
module HighlightSearchParams
|
5
|
+
# add highlights on full text field, if there is a keyword query
|
6
|
+
def highlight_search_params(solr_parameters = {})
|
7
|
+
return unless solr_parameters[:q] || solr_parameters[:all_fields]
|
8
|
+
solr_parameters[:hl] = true
|
9
|
+
solr_parameters[:'hl.fl'] = 'all_text_tsimv'
|
10
|
+
solr_parameters[:'hl.fragsize'] = 100
|
11
|
+
solr_parameters[:'hl.snippets'] = 5
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module IiifPrint
|
2
|
+
module ManifestBuilderServiceBehavior
|
3
|
+
def initialize(*args,
|
4
|
+
version: IiifPrint.config.default_iiif_manifest_version,
|
5
|
+
iiif_manifest_factory: iiif_manifest_factory_for(version),
|
6
|
+
&block)
|
7
|
+
super(*args, iiif_manifest_factory: iiif_manifest_factory, &block)
|
8
|
+
@version = version.to_i
|
9
|
+
end
|
10
|
+
|
11
|
+
def manifest_for(presenter:)
|
12
|
+
build_manifest(presenter: presenter)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
VERSION_TO_MANIFEST_FACTORY_MAP = {
|
18
|
+
2 => ::IIIFManifest::ManifestFactory,
|
19
|
+
3 => ::IIIFManifest::V3::ManifestFactory
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
def iiif_manifest_factory_for(version)
|
23
|
+
VERSION_TO_MANIFEST_FACTORY_MAP.fetch(version.to_i)
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Allows for the display of metadata for child works in UV
|
28
|
+
#
|
29
|
+
# @see https://github.com/samvera/hyrax/blob/main/app/services/hyrax/manifest_builder_service.rb
|
30
|
+
def build_manifest(presenter:)
|
31
|
+
# ::IIIFManifest::ManifestBuilder#to_h returns a
|
32
|
+
# IIIFManifest::ManifestBuilder::IIIFManifest, not a Hash.
|
33
|
+
# to get a Hash, we have to call its #to_json, then parse.
|
34
|
+
#
|
35
|
+
# wild times. maybe there's a better way to do this with the
|
36
|
+
# ManifestFactory interface?
|
37
|
+
manifest = manifest_factory.new(presenter).to_h
|
38
|
+
hash = JSON.parse(manifest.to_json)
|
39
|
+
hash = send("sanitize_v#{@version}", hash: hash, presenter: presenter)
|
40
|
+
send("sorted_canvases_v#{@version}", hash: hash, sort_field: IiifPrint.config.sort_iiif_manifest_canvases_by)
|
41
|
+
end
|
42
|
+
|
43
|
+
def sanitize_v2(hash:, presenter:)
|
44
|
+
hash['label'] = CGI.unescapeHTML(sanitize_value(hash['label'])) if hash.key?('label')
|
45
|
+
hash.delete('description') # removes default description since it's in the metadata fields
|
46
|
+
hash['sequences']&.each do |sequence|
|
47
|
+
sequence['canvases']&.each do |canvas|
|
48
|
+
canvas['label'] = CGI.unescapeHTML(sanitize_value(canvas['label']))
|
49
|
+
apply_v2_metadata_to_canvas(canvas: canvas, presenter: presenter)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
hash
|
53
|
+
end
|
54
|
+
|
55
|
+
def sanitize_v3(hash:, **)
|
56
|
+
# TODO: flesh out metadata for v3
|
57
|
+
hash
|
58
|
+
end
|
59
|
+
|
60
|
+
def apply_v2_metadata_to_canvas(canvas:, presenter:)
|
61
|
+
solr_docs = get_solr_docs(presenter)
|
62
|
+
# uses the '@id' property which is a URL that contains the FileSet id
|
63
|
+
file_set_id = canvas['@id'].split('/').last
|
64
|
+
# finds the image that the FileSet is attached to and creates metadata on that canvas
|
65
|
+
image = solr_docs.find { |doc| doc[:member_ids_ssim]&.include?(file_set_id) }
|
66
|
+
canvas_metadata = IiifPrint.manifest_metadata_for(work: image,
|
67
|
+
current_ability: presenter.ability,
|
68
|
+
base_url: presenter.base_url)
|
69
|
+
canvas['metadata'] = canvas_metadata
|
70
|
+
end
|
71
|
+
|
72
|
+
def sorted_canvases_v2(hash:, sort_field:)
|
73
|
+
sort_field = Hyrax::Renderers::AttributeRenderer.new(sort_field, nil).label
|
74
|
+
hash["sequences"]&.first&.[]("canvases")&.sort_by! do |canvas|
|
75
|
+
selection = canvas["metadata"].select { |h| h["label"] == sort_field }
|
76
|
+
fallback = [{ label: sort_field, value: ['~'] }]
|
77
|
+
identifier_metadata = selection.presence || fallback
|
78
|
+
identifier_metadata.first["value"] if identifier_metadata.present?
|
79
|
+
end
|
80
|
+
hash
|
81
|
+
end
|
82
|
+
|
83
|
+
def sorted_canvases_v3(hash:, **)
|
84
|
+
# TODO: flesh out metadata for v3
|
85
|
+
hash
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_solr_docs(presenter)
|
89
|
+
parent_id = [presenter._source['id']]
|
90
|
+
child_ids = presenter._source['member_ids_ssim']
|
91
|
+
parent_id_and_child_ids = parent_id + child_ids
|
92
|
+
query = ActiveFedora::SolrQueryBuilder.construct_query_for_ids(parent_id_and_child_ids)
|
93
|
+
solr_hits = ActiveFedora::SolrService.query(query, fq: "-has_model_ssim:FileSet", rows: 100_000)
|
94
|
+
solr_hits.map { |solr_hit| ::SolrDocument.new(solr_hit) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# General derivative service for IiifPrint, which is meant to wrap
|
2
|
+
# and replace the stock Hyrax::FileSetDerivativeService with a proxy
|
3
|
+
# that runs one or more derivative service "plugin" components.
|
4
|
+
#
|
5
|
+
# Note: Hyrax::DerivativeService consumes this, instead of (directly)
|
6
|
+
# consuming Hyrax::FileSetDerivativeService.
|
7
|
+
#
|
8
|
+
# Unlike the "run the first valid plugin" arrangement that the
|
9
|
+
# Hyrax::DerivativeService uses to run an actual derivative creation
|
10
|
+
# service component, this component is:
|
11
|
+
#
|
12
|
+
# (a) Consumed by Hyrax::DerivativeService as that first valid plugin;
|
13
|
+
#
|
14
|
+
# (b) Wraps and runs 0..* plugins, not just the first.
|
15
|
+
#
|
16
|
+
# This should be registered to take precedence over default by:
|
17
|
+
# Hyrax::DerivativeService.services.unshift(
|
18
|
+
# IiifPrint::PluggableDerivativeService
|
19
|
+
# )
|
20
|
+
#
|
21
|
+
# Modify IiifPrint::PluggableDerivativeService.plugins
|
22
|
+
# to add, remove, or reorder plugin (derivative service) classes.
|
23
|
+
#
|
24
|
+
class IiifPrint::PluggableDerivativeService
|
25
|
+
class_attribute :allowed_methods, default: [:cleanup_derivatives, :create_derivatives]
|
26
|
+
class_attribute :default_plugins, default: [Hyrax::FileSetDerivativesService]
|
27
|
+
class_attribute :derivative_path_factory, default: Hyrax::DerivativePath
|
28
|
+
|
29
|
+
def initialize(file_set, plugins: plugins_for(file_set))
|
30
|
+
@file_set = file_set
|
31
|
+
@plugins = Array.wrap(plugins)
|
32
|
+
@valid_plugins = plugins.map { |plugin| plugin.new(file_set) }.select(&:valid?)
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :file_set, :plugins, :valid_plugins
|
36
|
+
delegate :uri, :mime_type, to: :file_set
|
37
|
+
|
38
|
+
# this wrapper/proxy/composite is always valid, but it may compose
|
39
|
+
# multiple plugins, some of which may or may not be valid, so
|
40
|
+
# validity checks happen within as well.
|
41
|
+
def valid?
|
42
|
+
!valid_plugins.size.zero?
|
43
|
+
end
|
44
|
+
|
45
|
+
# get derivative services relevant to method name and file_set context
|
46
|
+
# -- omits plugins if particular destination exists or will soon.
|
47
|
+
def services(method_name)
|
48
|
+
valid_plugins.select do |plugin|
|
49
|
+
dest = nil
|
50
|
+
dest = plugin.target_extension if plugin.respond_to?(:target_extension)
|
51
|
+
!skip_destination?(method_name, dest)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def respond_to_missing?(method_name, include_private = false)
|
58
|
+
allowed_methods.include?(method_name) || super
|
59
|
+
end
|
60
|
+
|
61
|
+
def method_missing(method_name, *args, **opts, &block)
|
62
|
+
if allowed_methods.include?(method_name)
|
63
|
+
# we have an allowed method, construct services and include all valid
|
64
|
+
# services for the file_set
|
65
|
+
# services = plugins.map { |plugin| plugin.new(file_set) }.select(&:valid?)
|
66
|
+
# run all valid services, in order:
|
67
|
+
services(method_name).each do |plugin|
|
68
|
+
plugin.send(method_name, *args, **opts, &block)
|
69
|
+
end
|
70
|
+
else
|
71
|
+
super
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def skip_destination?(method_name, destination_name)
|
76
|
+
return false unless method_name == :create_derivatives
|
77
|
+
return false unless destination_name
|
78
|
+
# NOTE: What are we after with this nil test? Are we looking for persisted objects?
|
79
|
+
return false if file_set.id.nil?
|
80
|
+
|
81
|
+
# skip :create_derivatives if existing --> do not re-create
|
82
|
+
existing_derivative?(destination_name) ||
|
83
|
+
impending_derivative?(destination_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def existing_derivative?(name)
|
87
|
+
path = derivative_path_factory.derivative_path_for_reference(
|
88
|
+
file_set,
|
89
|
+
name
|
90
|
+
)
|
91
|
+
File.exist?(path)
|
92
|
+
end
|
93
|
+
|
94
|
+
# is there an impending attachment from ingest logged to db?
|
95
|
+
# -- avoids stomping over pre-made derivative
|
96
|
+
# for which an attachment is still in-progress.
|
97
|
+
def impending_derivative?(name)
|
98
|
+
IiifPrint::DerivativeAttachment.exists?(
|
99
|
+
fileset_id: file_set.id,
|
100
|
+
destination_name: name
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
# This method is responsible for determine what are the possible plugins / services that this file
|
105
|
+
# set would use. That "possibility" is based on the work. Later, we will check the plugin's
|
106
|
+
# "valid?" which would now look at the specific file_set for validity.
|
107
|
+
def plugins_for(file_set)
|
108
|
+
parent = parent_for(file_set)
|
109
|
+
return Array(default_plugins) if parent.nil?
|
110
|
+
return Array(default_plugins) unless parent.respond_to?(:iiif_print_config)
|
111
|
+
|
112
|
+
(file_set.parent.iiif_print_config.derivative_service_plugins + Array(default_plugins)).flatten.compact.uniq
|
113
|
+
end
|
114
|
+
|
115
|
+
def parent_for(file_set)
|
116
|
+
# fallback to Fedora-stored relationships if work's aggregation of
|
117
|
+
# file set is not indexed in Solr
|
118
|
+
file_set.parent || file_set.member_of.find(&:work?)
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%# additional ocr snippets, with a Bootstrap collapse toggle control %>
|
2
|
+
<% document_id = options[:document].id %>
|
3
|
+
<div class="collapse ocr_snippet" id="<%= "snippet_collapse_#{document_id}" %>">
|
4
|
+
<% snippets.each do |snippet| %>
|
5
|
+
<%= content_tag('div',
|
6
|
+
"... #{snippet} ...".html_safe,
|
7
|
+
class: 'ocr_snippet') %>
|
8
|
+
<% end %>
|
9
|
+
</div>
|
10
|
+
<%= link_to(t('blacklight.search.results.snippets.more'),
|
11
|
+
"#snippet_collapse_#{document_id}",
|
12
|
+
data: {toggle: 'collapse'},
|
13
|
+
'aria-expanded' => 'false',
|
14
|
+
'aria-controls' => "#snippet_collapse_#{document_id}",
|
15
|
+
class: 'ocr_snippets_expand js-controls')
|
16
|
+
%>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<% if presenter.iiif_viewer? %>
|
2
|
+
<% if defined?(viewer) && viewer %>
|
3
|
+
<%= iiif_viewer_display presenter %>
|
4
|
+
<% else %>
|
5
|
+
<%= render media_display_partial(presenter.representative_presenter), file_set: presenter.representative_presenter %>
|
6
|
+
<% end %>
|
7
|
+
<% else %>
|
8
|
+
<%= image_tag 'default.png', class: "canonical-image", alt: 'default representative image' %>
|
9
|
+
<% end %>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
<div class="viewer-wrapper">
|
2
|
+
<iframe
|
3
|
+
id="uv-iframe"
|
4
|
+
src="<%= universal_viewer_base_url %>#?manifest=<%= main_app.polymorphic_url [main_app, :manifest, presenter], { locale: nil } %>&config=<%= universal_viewer_config_url %>"
|
5
|
+
allowfullscreen="true"
|
6
|
+
frameborder="0"
|
7
|
+
></iframe>
|
8
|
+
</div>
|