arclight 0.0.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.all-contributorsrc +450 -0
- data/.codeclimate.yml +5 -0
- data/.eslintrc +22 -0
- data/.github/workflows/ruby.yml +51 -0
- data/.gitignore +7 -0
- data/.rspec +0 -1
- data/.rubocop.yml +521 -0
- data/.rubocop_todo.yml +122 -0
- data/.solr_wrapper +6 -0
- data/CONTRIBUTING.md +43 -0
- data/CONTRIBUTORS.md +79 -0
- data/Gemfile +41 -0
- data/LICENSE.txt +1 -0
- data/README.md +100 -12
- data/Rakefile +13 -3
- data/app/assets/images/arclight/logo.png +0 -0
- data/app/assets/images/blacklight/bookmark.svg +1 -0
- data/app/assets/images/blacklight/collection.svg +5 -0
- data/app/assets/images/blacklight/compact.svg +1 -0
- data/app/assets/images/blacklight/container.svg +5 -0
- data/app/assets/images/blacklight/ead.svg +1 -0
- data/app/assets/images/blacklight/file.svg +5 -0
- data/app/assets/images/blacklight/folder.svg +1 -0
- data/app/assets/images/blacklight/list.svg +1 -0
- data/app/assets/images/blacklight/minus.svg +1 -0
- data/app/assets/images/blacklight/online.svg +5 -0
- data/app/assets/images/blacklight/pdf.svg +1 -0
- data/app/assets/images/blacklight/plus.svg +1 -0
- data/app/assets/images/blacklight/repository.svg +1 -0
- data/app/assets/javascripts/arclight/arclight.js +4 -0
- data/app/assets/javascripts/arclight/oembed_controller.js +58 -0
- data/app/assets/javascripts/arclight/truncate_controller.js +27 -0
- data/app/assets/stylesheets/arclight/application.scss +14 -0
- data/app/assets/stylesheets/arclight/build.scss +4 -0
- data/app/assets/stylesheets/arclight/modules/collection_search.scss +3 -0
- data/app/assets/stylesheets/arclight/modules/hierarchy_and_online_contents.scss +155 -0
- data/app/assets/stylesheets/arclight/modules/highlights.scss +11 -0
- data/app/assets/stylesheets/arclight/modules/icons.scss +20 -0
- data/app/assets/stylesheets/arclight/modules/layout.scss +220 -0
- data/app/assets/stylesheets/arclight/modules/mastheads.scss +42 -0
- data/app/assets/stylesheets/arclight/modules/repositories.scss +25 -0
- data/app/assets/stylesheets/arclight/modules/repository_card.scss +9 -0
- data/app/assets/stylesheets/arclight/modules/search_form.scss +9 -0
- data/app/assets/stylesheets/arclight/modules/search_results.scss +250 -0
- data/app/assets/stylesheets/arclight/modules/show_collection.scss +38 -0
- data/app/assets/stylesheets/arclight/modules/truncator.scss +58 -0
- data/app/assets/stylesheets/arclight/responsive.scss +13 -0
- data/app/assets/stylesheets/arclight/variables.scss +25 -0
- data/app/components/arclight/access_component.html.erb +14 -0
- data/app/components/arclight/access_component.rb +25 -0
- data/app/components/arclight/bookmark_component.html.erb +25 -0
- data/app/components/arclight/bookmark_component.rb +9 -0
- data/app/components/arclight/breadcrumb_component.rb +50 -0
- data/app/components/arclight/breadcrumbs_hierarchy_component.html.erb +34 -0
- data/app/components/arclight/breadcrumbs_hierarchy_component.rb +25 -0
- data/app/components/arclight/collection_context_component.html.erb +12 -0
- data/app/components/arclight/collection_context_component.rb +27 -0
- data/app/components/arclight/collection_info_component.html.erb +28 -0
- data/app/components/arclight/collection_info_component.rb +26 -0
- data/app/components/arclight/collection_sidebar_component.html.erb +15 -0
- data/app/components/arclight/collection_sidebar_component.rb +39 -0
- data/app/components/arclight/constraints_component.html.erb +7 -0
- data/app/components/arclight/constraints_component.rb +17 -0
- data/app/components/arclight/document_collection_context_component.html.erb +29 -0
- data/app/components/arclight/document_collection_context_component.rb +28 -0
- data/app/components/arclight/document_collection_hierarchy_component.html.erb +50 -0
- data/app/components/arclight/document_collection_hierarchy_component.rb +62 -0
- data/app/components/arclight/document_component.html.erb +39 -0
- data/app/components/arclight/document_component.rb +49 -0
- data/app/components/arclight/document_components_hierarchy_component.html.erb +20 -0
- data/app/components/arclight/document_components_hierarchy_component.rb +34 -0
- data/app/components/arclight/document_download_component.html.erb +24 -0
- data/app/components/arclight/document_download_component.rb +71 -0
- data/app/components/arclight/embed_component.html.erb +10 -0
- data/app/components/arclight/embed_component.rb +43 -0
- data/app/components/arclight/expand_hierarchy_button_component.html.erb +5 -0
- data/app/components/arclight/expand_hierarchy_button_component.rb +16 -0
- data/app/components/arclight/group_component.html.erb +34 -0
- data/app/components/arclight/group_component.rb +23 -0
- data/app/components/arclight/header_component.html.erb +5 -0
- data/app/components/arclight/header_component.rb +10 -0
- data/app/components/arclight/index_metadata_field_component.html.erb +16 -0
- data/app/components/arclight/index_metadata_field_component.rb +20 -0
- data/app/components/arclight/masthead_component.html.erb +17 -0
- data/app/components/arclight/masthead_component.rb +10 -0
- data/app/components/arclight/metadata_section_component.html.erb +13 -0
- data/app/components/arclight/metadata_section_component.rb +22 -0
- data/app/components/arclight/oembed_viewer_component.html.erb +5 -0
- data/app/components/arclight/oembed_viewer_component.rb +16 -0
- data/app/components/arclight/online_content_filter_component.html.erb +15 -0
- data/app/components/arclight/online_content_filter_component.rb +17 -0
- data/app/components/arclight/online_status_indicator_component.rb +19 -0
- data/app/components/arclight/repository_breadcrumb_component.html.erb +10 -0
- data/app/components/arclight/repository_breadcrumb_component.rb +17 -0
- data/app/components/arclight/repository_location_component.html.erb +20 -0
- data/app/components/arclight/repository_location_component.rb +10 -0
- data/app/components/arclight/search_bar_component.html.erb +27 -0
- data/app/components/arclight/search_bar_component.rb +32 -0
- data/app/components/arclight/search_result_breadcrumbs_component.html.erb +6 -0
- data/app/components/arclight/search_result_breadcrumbs_component.rb +37 -0
- data/app/components/arclight/search_result_component.html.erb +20 -0
- data/app/components/arclight/search_result_component.rb +18 -0
- data/app/components/arclight/search_result_title_component.html.erb +15 -0
- data/app/components/arclight/search_result_title_component.rb +15 -0
- data/app/components/arclight/sidebar_component.html.erb +9 -0
- data/app/components/arclight/sidebar_component.rb +19 -0
- data/app/components/arclight/upper_metadata_layout_component.rb +10 -0
- data/app/components/blacklight/icons/bookmark_component.rb +12 -0
- data/app/components/blacklight/icons/collection_component.rb +14 -0
- data/app/components/blacklight/icons/compact_component.rb +12 -0
- data/app/components/blacklight/icons/container_component.rb +14 -0
- data/app/components/blacklight/icons/ead_component.rb +12 -0
- data/app/components/blacklight/icons/file_component.rb +14 -0
- data/app/components/blacklight/icons/folder_component.rb +12 -0
- data/app/components/blacklight/icons/online_component.rb +15 -0
- data/app/components/blacklight/icons/pdf_component.rb +12 -0
- data/app/components/blacklight/icons/repository_component.rb +12 -0
- data/app/controllers/arclight/repositories_controller.rb +40 -0
- data/app/helpers/arclight/ead_format_helpers.rb +309 -0
- data/app/helpers/arclight/field_config_helpers.rb +26 -0
- data/app/helpers/arclight_helper.rb +108 -0
- data/app/models/arclight/document_downloads.rb +122 -0
- data/app/models/arclight/parent.rb +24 -0
- data/app/models/arclight/parents.rb +39 -0
- data/app/models/arclight/requests/aeon_external_request.rb +42 -0
- data/app/models/arclight/requests/aeon_web_ead.rb +52 -0
- data/app/models/arclight/requests/google_form.rb +49 -0
- data/app/models/concerns/arclight/catalog.rb +44 -0
- data/app/models/concerns/arclight/search_behavior.rb +47 -0
- data/app/models/concerns/arclight/solr_document.rb +160 -0
- data/app/presenters/arclight/index_presenter.rb +10 -0
- data/app/presenters/arclight/show_presenter.rb +54 -0
- data/app/views/arclight/.keep +0 -0
- data/app/views/arclight/_requests.html.erb +7 -0
- data/app/views/arclight/repositories/_repository.html.erb +43 -0
- data/app/views/arclight/repositories/index.html.erb +9 -0
- data/app/views/arclight/repositories/show.html.erb +37 -0
- data/app/views/arclight/requests/_aeon_external_request_endpoint.html.erb +8 -0
- data/app/views/arclight/requests/_aeon_web_ead.html.erb +6 -0
- data/app/views/arclight/requests/_google_form.html.erb +10 -0
- data/app/views/catalog/_document_list.html.erb +8 -0
- data/app/views/catalog/_group.html.erb +4 -0
- data/app/views/catalog/_group_toggle.html.erb +10 -0
- data/app/views/catalog/_search_results_header.html.erb +3 -0
- data/app/views/catalog/hierarchy.html.erb +19 -0
- data/app/views/catalog/index.html.erb +17 -0
- data/app/views/shared/_breadcrumbs.html.erb +3 -0
- data/app/views/shared/_main_menu_links.html.erb +6 -0
- data/arclight.gemspec +29 -11
- data/bin/rails +13 -0
- data/config/breadcrumbs.rb +24 -0
- data/config/i18n-tasks.yml +134 -0
- data/config/importmap.rb +3 -0
- data/config/locales/arclight.en.yml +100 -0
- data/config/repositories.yml +0 -0
- data/config/routes.rb +6 -0
- data/docker-compose.yml +16 -0
- data/lib/arclight/digital_object.rb +27 -0
- data/lib/arclight/engine.rb +47 -0
- data/lib/arclight/exceptions.rb +19 -0
- data/lib/arclight/hash_absolute_xpath.rb +62 -0
- data/lib/arclight/level_label.rb +46 -0
- data/lib/arclight/missing_id_strategy.rb +21 -0
- data/lib/arclight/normalized_date.rb +49 -0
- data/lib/arclight/normalized_id.rb +30 -0
- data/lib/arclight/normalized_title.rb +33 -0
- data/lib/arclight/repository.rb +116 -0
- data/lib/arclight/routes/hierarchy.rb +19 -0
- data/lib/arclight/routes.rb +8 -0
- data/lib/arclight/traject/ead2_component_config.rb +335 -0
- data/lib/arclight/traject/ead2_config.rb +316 -0
- data/lib/arclight/traject/nokogiri_namespaceless_reader.rb +22 -0
- data/lib/arclight/version.rb +3 -1
- data/lib/arclight/year_range.rb +111 -0
- data/lib/arclight.rb +11 -1
- data/lib/generators/arclight/install_generator.rb +163 -0
- data/lib/generators/arclight/templates/arclight.scss +6 -0
- data/lib/generators/arclight/templates/catalog_controller.rb +423 -0
- data/lib/generators/arclight/templates/config/downloads.yml +25 -0
- data/lib/generators/arclight/templates/config/locales/arclight.en.yml +61 -0
- data/lib/generators/arclight/templates/config/repositories.yml +53 -0
- data/lib/generators/arclight/update_generator.rb +22 -0
- data/lib/tasks/index.rake +84 -0
- data/package.json +30 -0
- data/solr/conf/_rest_managed.json +3 -0
- data/solr/conf/admin-extra.html +31 -0
- data/solr/conf/elevate.xml +36 -0
- data/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
- data/solr/conf/protwords.txt +21 -0
- data/solr/conf/schema.xml +400 -0
- data/solr/conf/solrconfig.xml +328 -0
- data/solr/conf/spellings.txt +2 -0
- data/solr/conf/stopwords.txt +58 -0
- data/solr/conf/stopwords_en.txt +58 -0
- data/solr/conf/synonyms.txt +31 -0
- data/solr/conf/xslt/example.xsl +132 -0
- data/solr/conf/xslt/example_atom.xsl +67 -0
- data/solr/conf/xslt/example_rss.xsl +66 -0
- data/solr/conf/xslt/luke.xsl +337 -0
- data/tasks/arclight.rake +79 -0
- data/template.rb +10 -0
- metadata +440 -20
- data/.travis.yml +0 -5
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'traject'
|
|
5
|
+
require 'traject/nokogiri_reader'
|
|
6
|
+
require 'traject_plus'
|
|
7
|
+
require 'traject_plus/macros'
|
|
8
|
+
require 'arclight/level_label'
|
|
9
|
+
require 'arclight/normalized_id'
|
|
10
|
+
require 'arclight/normalized_date'
|
|
11
|
+
require 'arclight/normalized_title'
|
|
12
|
+
require 'active_model/conversion' ## Needed for Arclight::Repository
|
|
13
|
+
require 'active_support/core_ext/array/wrap'
|
|
14
|
+
require 'arclight/digital_object'
|
|
15
|
+
require 'arclight/year_range'
|
|
16
|
+
require 'arclight/repository'
|
|
17
|
+
require 'arclight/traject/nokogiri_namespaceless_reader'
|
|
18
|
+
|
|
19
|
+
# rubocop:disable Style/MixinUsage
|
|
20
|
+
extend TrajectPlus::Macros
|
|
21
|
+
# rubocop:enable Style/MixinUsage
|
|
22
|
+
|
|
23
|
+
NAME_ELEMENTS = %w[corpname famname name persname].freeze
|
|
24
|
+
|
|
25
|
+
SEARCHABLE_NOTES_FIELDS = %w[
|
|
26
|
+
accessrestrict
|
|
27
|
+
accruals
|
|
28
|
+
altformavail
|
|
29
|
+
appraisal
|
|
30
|
+
arrangement
|
|
31
|
+
bibliography
|
|
32
|
+
bioghist
|
|
33
|
+
custodhist
|
|
34
|
+
fileplan
|
|
35
|
+
note
|
|
36
|
+
odd
|
|
37
|
+
originalsloc
|
|
38
|
+
otherfindaid
|
|
39
|
+
phystech
|
|
40
|
+
prefercite
|
|
41
|
+
processinfo
|
|
42
|
+
relatedmaterial
|
|
43
|
+
scopecontent
|
|
44
|
+
separatedmaterial
|
|
45
|
+
userestrict
|
|
46
|
+
].freeze
|
|
47
|
+
|
|
48
|
+
DID_SEARCHABLE_NOTES_FIELDS = %w[
|
|
49
|
+
abstract
|
|
50
|
+
materialspec
|
|
51
|
+
physloc
|
|
52
|
+
note
|
|
53
|
+
].freeze
|
|
54
|
+
|
|
55
|
+
settings do
|
|
56
|
+
provide 'component_traject_config', File.join(__dir__, 'ead2_component_config.rb')
|
|
57
|
+
provide 'id_normalizer', 'Arclight::NormalizedId'
|
|
58
|
+
provide 'date_normalizer', 'Arclight::NormalizedDate'
|
|
59
|
+
provide 'title_normalizer', 'Arclight::NormalizedTitle'
|
|
60
|
+
provide 'reader_class_name', 'Arclight::Traject::NokogiriNamespacelessReader'
|
|
61
|
+
provide 'solr_writer.commit_on_close', 'true'
|
|
62
|
+
provide 'repository', ENV.fetch('REPOSITORY_ID', nil)
|
|
63
|
+
provide 'logger', Logger.new($stderr)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
each_record do |_record, context|
|
|
67
|
+
next unless settings['repository']
|
|
68
|
+
|
|
69
|
+
context.clipboard[:repository] = Arclight::Repository.find_by(
|
|
70
|
+
slug: settings['repository']
|
|
71
|
+
).name
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# ==================
|
|
75
|
+
# Top level document
|
|
76
|
+
#
|
|
77
|
+
# NOTE: All fields should be stored in Solr
|
|
78
|
+
# ==================
|
|
79
|
+
|
|
80
|
+
to_field 'id' do |record, accumulator|
|
|
81
|
+
id = record.at_xpath('/ead/eadheader/eadid')&.text
|
|
82
|
+
unitid = record.at_xpath('/ead/archdesc/did/unitid')&.text
|
|
83
|
+
title = record.at_xpath('/ead/archdesc/did/unittitle')&.text
|
|
84
|
+
repository = settings['repository']
|
|
85
|
+
accumulator << settings['id_normalizer'].constantize.new(id, unitid: unitid, title: title, repository: repository).to_s
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
to_field 'title_filing_ssi', extract_xpath('/ead/eadheader/filedesc/titlestmt/titleproper[@type="filing"]')
|
|
89
|
+
to_field 'title_ssm', extract_xpath('/ead/archdesc/did/unittitle')
|
|
90
|
+
to_field 'title_tesim', extract_xpath('/ead/archdesc/did/unittitle')
|
|
91
|
+
to_field 'ead_ssi', extract_xpath('/ead/eadheader/eadid')
|
|
92
|
+
|
|
93
|
+
to_field 'unitdate_ssm', extract_xpath('/ead/archdesc/did/unitdate')
|
|
94
|
+
to_field 'unitdate_bulk_ssim', extract_xpath('/ead/archdesc/did/unitdate[@type="bulk"]')
|
|
95
|
+
to_field 'unitdate_inclusive_ssm', extract_xpath('/ead/archdesc/did/unitdate[@type="inclusive"]')
|
|
96
|
+
to_field 'unitdate_other_ssim', extract_xpath('/ead/archdesc/did/unitdate[not(@type)]')
|
|
97
|
+
|
|
98
|
+
# All top-level docs treated as 'collection' for routing / display purposes
|
|
99
|
+
to_field 'level_ssm' do |_record, accumulator|
|
|
100
|
+
accumulator << 'collection'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Keep the original top-level archdesc/@level for Level facet in addition to 'Collection'
|
|
104
|
+
to_field 'level_ssim' do |record, accumulator|
|
|
105
|
+
level = record.at_xpath('/ead/archdesc').attribute('level')&.value
|
|
106
|
+
other_level = record.at_xpath('/ead/archdesc').attribute('otherlevel')&.value
|
|
107
|
+
|
|
108
|
+
accumulator << Arclight::LevelLabel.new(level, other_level).to_s
|
|
109
|
+
accumulator << 'Collection' unless level == 'collection'
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
to_field 'unitid_ssm', extract_xpath('/ead/archdesc/did/unitid')
|
|
113
|
+
to_field 'unitid_tesim', extract_xpath('/ead/archdesc/did/unitid')
|
|
114
|
+
|
|
115
|
+
to_field 'normalized_date_ssm' do |_record, accumulator, context|
|
|
116
|
+
accumulator << settings['date_normalizer'].constantize.new(
|
|
117
|
+
context.output_hash['unitdate_inclusive_ssm'],
|
|
118
|
+
context.output_hash['unitdate_bulk_ssim'],
|
|
119
|
+
context.output_hash['unitdate_other_ssim']
|
|
120
|
+
).to_s
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
to_field 'normalized_title_ssm' do |_record, accumulator, context|
|
|
124
|
+
title = context.output_hash['title_ssm']&.first
|
|
125
|
+
date = context.output_hash['normalized_date_ssm']&.first
|
|
126
|
+
accumulator << settings['title_normalizer'].constantize.new(title, date).to_s
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
to_field 'collection_title_tesim' do |_record, accumulator, context|
|
|
130
|
+
accumulator.concat context.output_hash.fetch('normalized_title_ssm', [])
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
to_field 'collection_ssim' do |_record, accumulator, context|
|
|
134
|
+
accumulator.concat context.output_hash.fetch('normalized_title_ssm', [])
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
to_field 'repository_ssm' do |_record, accumulator, context|
|
|
138
|
+
accumulator << context.clipboard[:repository]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
to_field 'repository_ssim' do |_record, accumulator, context|
|
|
142
|
+
accumulator << context.clipboard[:repository]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
to_field 'geogname_ssm', extract_xpath('/ead/archdesc/controlaccess/geogname')
|
|
146
|
+
to_field 'geogname_ssim', extract_xpath('/ead/archdesc/controlaccess/geogname')
|
|
147
|
+
|
|
148
|
+
to_field 'creator_ssm', extract_xpath('/ead/archdesc/did/origination')
|
|
149
|
+
to_field 'creator_ssim', extract_xpath('/ead/archdesc/did/origination')
|
|
150
|
+
to_field 'creator_sort' do |record, accumulator|
|
|
151
|
+
accumulator << record.xpath('/ead/archdesc/did/origination').map { |c| c.text.strip }.join(', ')
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
to_field 'creator_persname_ssim', extract_xpath('/ead/archdesc/did/origination/persname')
|
|
155
|
+
to_field 'creator_corpname_ssim', extract_xpath('/ead/archdesc/did/origination/corpname')
|
|
156
|
+
to_field 'creator_famname_ssim', extract_xpath('/ead/archdesc/did/origination/famname')
|
|
157
|
+
|
|
158
|
+
to_field 'creators_ssim' do |_record, accumulator, context|
|
|
159
|
+
accumulator.concat context.output_hash['creator_persname_ssim'] if context.output_hash['creator_persname_ssim']
|
|
160
|
+
accumulator.concat context.output_hash['creator_corpname_ssim'] if context.output_hash['creator_corpname_ssim']
|
|
161
|
+
accumulator.concat context.output_hash['creator_famname_ssim'] if context.output_hash['creator_famname_ssim']
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
to_field 'places_ssim', extract_xpath('/ead/archdesc/controlaccess/geogname')
|
|
165
|
+
|
|
166
|
+
to_field 'access_terms_ssm', extract_xpath('/ead/archdesc/userestrict/*[local-name()!="head"]')
|
|
167
|
+
|
|
168
|
+
to_field 'acqinfo_ssim', extract_xpath('/ead/archdesc/acqinfo/*[local-name()!="head"]')
|
|
169
|
+
to_field 'acqinfo_ssim', extract_xpath('/ead/archdesc/descgrp/acqinfo/*[local-name()!="head"]')
|
|
170
|
+
|
|
171
|
+
to_field 'access_subjects_ssim', extract_xpath('/ead/archdesc/controlaccess', to_text: false) do |_record, accumulator|
|
|
172
|
+
accumulator.map! do |element|
|
|
173
|
+
%w[subject function occupation genreform].map do |selector|
|
|
174
|
+
element.xpath(".//#{selector}").map(&:text)
|
|
175
|
+
end
|
|
176
|
+
end.flatten!
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
to_field 'access_subjects_ssm' do |_record, accumulator, context|
|
|
180
|
+
accumulator.concat Array.wrap(context.output_hash['access_subjects_ssim'])
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
to_field 'has_online_content_ssim', extract_xpath('.//dao') do |_record, accumulator|
|
|
184
|
+
accumulator.replace([accumulator.any?])
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
to_field 'digital_objects_ssm', extract_xpath('/ead/archdesc/did/dao|/ead/archdesc/dao', to_text: false) do |_record, accumulator|
|
|
188
|
+
accumulator.map! do |dao|
|
|
189
|
+
label = dao.attributes['title']&.value ||
|
|
190
|
+
dao.xpath('daodesc/p')&.text
|
|
191
|
+
href = (dao.attributes['href'] || dao.attributes['xlink:href'])&.value
|
|
192
|
+
Arclight::DigitalObject.new(label: label, href: href).to_json
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# This accumulates direct text from a physdesc, ignoring child elements handled elsewhere
|
|
197
|
+
to_field 'physdesc_tesim', extract_xpath('/ead/archdesc/did/physdesc', to_text: false) do |_record, accumulator|
|
|
198
|
+
accumulator.map! do |element|
|
|
199
|
+
physdesc = []
|
|
200
|
+
element.children.map do |child|
|
|
201
|
+
next if child.instance_of?(Nokogiri::XML::Element)
|
|
202
|
+
|
|
203
|
+
physdesc << child.text&.strip unless child.text&.strip&.empty?
|
|
204
|
+
end.flatten
|
|
205
|
+
physdesc.join(' ') unless physdesc.empty?
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
to_field 'extent_ssm' do |record, accumulator|
|
|
210
|
+
physdescs = record.xpath('/ead/archdesc/did/physdesc')
|
|
211
|
+
extents_per_physdesc = physdescs.map do |physdesc|
|
|
212
|
+
extents = physdesc.xpath('./extent').map { |e| e.text.strip }
|
|
213
|
+
# Join extents within the same physdesc with an empty string
|
|
214
|
+
extents.join(' ') unless extents.empty?
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Add each physdesc separately to the accumulator
|
|
218
|
+
accumulator.concat(extents_per_physdesc)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
to_field 'extent_tesim' do |_record, accumulator, context|
|
|
222
|
+
accumulator.concat context.output_hash['extent_ssm'] || []
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
to_field 'physfacet_tesim', extract_xpath('/ead/archdesc/did/physdesc/physfacet')
|
|
226
|
+
to_field 'dimensions_tesim', extract_xpath('/ead/archdesc/did/physdesc/dimensions')
|
|
227
|
+
|
|
228
|
+
to_field 'genreform_ssim', extract_xpath('/ead/archdesc/controlaccess/genreform')
|
|
229
|
+
|
|
230
|
+
to_field 'date_range_isim', extract_xpath('/ead/archdesc/did/unitdate/@normal', to_text: false) do |_record, accumulator|
|
|
231
|
+
range = Arclight::YearRange.new
|
|
232
|
+
next range.years if accumulator.blank?
|
|
233
|
+
|
|
234
|
+
ranges = accumulator.map(&:to_s)
|
|
235
|
+
range << range.parse_ranges(ranges)
|
|
236
|
+
accumulator.replace range.years
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
to_field 'indexes_html_tesm', extract_xpath('/ead/archdesc/index', to_text: false)
|
|
240
|
+
to_field 'indexes_tesim', extract_xpath('/ead/archdesc/index')
|
|
241
|
+
|
|
242
|
+
SEARCHABLE_NOTES_FIELDS.map do |selector|
|
|
243
|
+
to_field "#{selector}_html_tesm", extract_xpath("/ead/archdesc/#{selector}/*[local-name()!='head']", to_text: false) do |_record, accumulator|
|
|
244
|
+
accumulator.map!(&:to_html)
|
|
245
|
+
end
|
|
246
|
+
to_field "#{selector}_heading_ssm", extract_xpath("/ead/archdesc/#{selector}/head") unless selector == 'prefercite'
|
|
247
|
+
to_field "#{selector}_tesim", extract_xpath("/ead/archdesc/#{selector}/*[local-name()!='head']")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
DID_SEARCHABLE_NOTES_FIELDS.map do |selector|
|
|
251
|
+
to_field "#{selector}_html_tesm", extract_xpath("/ead/archdesc/did/#{selector}", to_text: false)
|
|
252
|
+
to_field "#{selector}_tesim", extract_xpath("/ead/archdesc/did/#{selector}")
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
NAME_ELEMENTS.map do |selector|
|
|
256
|
+
to_field 'names_coll_ssim', extract_xpath("/ead/archdesc/controlaccess/#{selector}")
|
|
257
|
+
to_field 'names_ssim', extract_xpath("//#{selector}"), unique
|
|
258
|
+
to_field "#{selector}_ssim", extract_xpath("//#{selector}"), unique
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
to_field 'language_ssim', extract_xpath('/ead/archdesc/did/langmaterial')
|
|
262
|
+
|
|
263
|
+
to_field 'descrules_ssm', extract_xpath('/ead/eadheader/profiledesc/descrules')
|
|
264
|
+
|
|
265
|
+
# count all descendant components from the top-level
|
|
266
|
+
to_field 'total_component_count_is', first_only do |record, accumulator|
|
|
267
|
+
accumulator << record.xpath('//c|//c01|//c02|//c03|//c04|//c05|//c06|//c07|//c08|//c09|//c10|//c11|//c12').count
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# count all digital objects from the top-level
|
|
271
|
+
to_field 'online_item_count_is', first_only do |record, accumulator|
|
|
272
|
+
accumulator << record.xpath('//dao').count
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
to_field 'component_level_isim' do |_record, accumulator, _context|
|
|
276
|
+
accumulator << 0
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
to_field 'sort_isi' do |_record, accumulator, _context|
|
|
280
|
+
accumulator << 0
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# =============================
|
|
284
|
+
# Each component child document
|
|
285
|
+
# <c> <c01> <c12>
|
|
286
|
+
# =============================
|
|
287
|
+
|
|
288
|
+
to_field 'components' do |record, accumulator, context|
|
|
289
|
+
child_components = record.xpath("/ead/archdesc/dsc/c|#{('/ead/archdesc/dsc/c01'..'/ead/archdesc/dsc/c12').to_a.join('|')}")
|
|
290
|
+
next unless child_components.any?
|
|
291
|
+
|
|
292
|
+
counter = Class.new do
|
|
293
|
+
def increment
|
|
294
|
+
@counter ||= 0
|
|
295
|
+
@counter += 1
|
|
296
|
+
end
|
|
297
|
+
end.new
|
|
298
|
+
|
|
299
|
+
component_indexer = Traject::Indexer::NokogiriIndexer.new.tap do |i|
|
|
300
|
+
i.settings do
|
|
301
|
+
provide :parent, context
|
|
302
|
+
provide :root, context
|
|
303
|
+
provide :counter, counter
|
|
304
|
+
provide :depth, 1
|
|
305
|
+
provide :logger, context.settings[:logger]
|
|
306
|
+
provide :component_traject_config, context.settings[:component_traject_config]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
i.load_config_file(context.settings[:component_traject_config])
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
child_components.each do |child_component|
|
|
313
|
+
output = component_indexer.map_record(child_component)
|
|
314
|
+
accumulator << output if output.keys.any?
|
|
315
|
+
end
|
|
316
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arclight
|
|
4
|
+
module Traject
|
|
5
|
+
# Provides a Traject Reader for XML Documents which removes the namespaces
|
|
6
|
+
class NokogiriNamespacelessReader < ::Traject::NokogiriReader
|
|
7
|
+
# Overrides the #each method (which is used for iterating through each Document)
|
|
8
|
+
# @param args
|
|
9
|
+
# @see ::Traject::NokogiriReader#each
|
|
10
|
+
# @see Enumerable#each
|
|
11
|
+
def each(*args)
|
|
12
|
+
return to_enum(:each, *args) unless block_given?
|
|
13
|
+
|
|
14
|
+
super do |doc|
|
|
15
|
+
new_doc = doc.dup
|
|
16
|
+
new_doc.remove_namespaces!
|
|
17
|
+
yield new_doc
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/arclight/version.rb
CHANGED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Arclight
|
|
4
|
+
# A range of years that handles gaps, such as [1999, 2000, 2002].
|
|
5
|
+
# Primary usage is:
|
|
6
|
+
# ```
|
|
7
|
+
# range = YearRange.new('1999/2004')
|
|
8
|
+
# range.years => [1999, 2000, 2001, 2002, 2003, 2004]
|
|
9
|
+
# range.to_s => '1999-2004'
|
|
10
|
+
# range << range.parse_ranges(['2010/2010'])
|
|
11
|
+
# range.years => [1999, 2000, 2001, 2002, 2003, 2004, 2010]
|
|
12
|
+
# range.to_s => '1999-2004, 2010'
|
|
13
|
+
# ```
|
|
14
|
+
class YearRange
|
|
15
|
+
attr_accessor :years
|
|
16
|
+
|
|
17
|
+
# @param [Array<String>] `dates` in the form YYYY/YYYY
|
|
18
|
+
def initialize(dates = nil)
|
|
19
|
+
@years = []
|
|
20
|
+
self << parse_ranges(dates) if dates.present?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [String] a concise, human-readable version of the year range, including gaps
|
|
24
|
+
def to_s
|
|
25
|
+
return if years.empty?
|
|
26
|
+
return to_s_for_streak(years) unless gaps?
|
|
27
|
+
|
|
28
|
+
to_s_with_gaps
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param [Array<Integer>] `other` the set of years to add
|
|
32
|
+
def <<(other)
|
|
33
|
+
return self if other.blank?
|
|
34
|
+
|
|
35
|
+
@years |= other # will remove duplicates
|
|
36
|
+
@years.sort!
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @param [String] `dates` in the form YYYY/YYYY
|
|
41
|
+
# @return [Array<Integer>] the set of years in the given range
|
|
42
|
+
def parse_range(dates)
|
|
43
|
+
return if dates.blank?
|
|
44
|
+
|
|
45
|
+
start_year, end_year = dates.split('/').map { |date| to_year_from_iso8601(date) }
|
|
46
|
+
return [start_year] if end_year.blank?
|
|
47
|
+
raise ArgumentError, "Range is too large: #{dates}" if (end_year - start_year) > 1000
|
|
48
|
+
raise ArgumentError, "Range is inverted: #{dates}" unless start_year <= end_year
|
|
49
|
+
|
|
50
|
+
(start_year..end_year).to_a
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param [Array<String>] `dates` in the form YYYY/YYYY
|
|
54
|
+
# @return [Array<Integer>] the set of years in the given range
|
|
55
|
+
def parse_ranges(dates)
|
|
56
|
+
dates.map { |date| parse_range(date) }.flatten.sort.uniq
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @param [String] `date` a date in one of these formats:
|
|
60
|
+
# YYYY, YYYY-MM, YYYY-MM-DD, and YYYYMMDD
|
|
61
|
+
def to_year_from_iso8601(date)
|
|
62
|
+
return if date.blank?
|
|
63
|
+
|
|
64
|
+
date.split('-').first[0..3].to_i # Time.parse doesn't work here
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [Boolean] are there gaps between the years, such as 1999, 2000, 2002?
|
|
68
|
+
def gaps?
|
|
69
|
+
return false if years.blank?
|
|
70
|
+
|
|
71
|
+
(years.min..years.max).to_a != years
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Deals with making a human-readable range for years with 1 or more gaps.
|
|
77
|
+
# It involves detection of streaks between the gaps.
|
|
78
|
+
# @return [String] 1999-2000, 2002 for 1999, 2000, 2002
|
|
79
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
80
|
+
def to_s_with_gaps
|
|
81
|
+
raise ArgumentError if years.blank? || years.length < 2
|
|
82
|
+
|
|
83
|
+
results = []
|
|
84
|
+
streak = [years[0]]
|
|
85
|
+
i = streak.first
|
|
86
|
+
years[1..].each do |j|
|
|
87
|
+
i += 1
|
|
88
|
+
if i == j
|
|
89
|
+
streak << j
|
|
90
|
+
else # we have a gap
|
|
91
|
+
results << if streak.length == 1
|
|
92
|
+
streak.first.to_s
|
|
93
|
+
else
|
|
94
|
+
to_s_for_streak(streak)
|
|
95
|
+
end
|
|
96
|
+
streak = [j]
|
|
97
|
+
i = j
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
results << to_s_for_streak(streak)
|
|
101
|
+
results.join(', ')
|
|
102
|
+
end
|
|
103
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
104
|
+
|
|
105
|
+
def to_s_for_streak(streak)
|
|
106
|
+
return streak.min.to_s if streak.min == streak.max
|
|
107
|
+
|
|
108
|
+
streak.minmax.map(&:to_s).join('-')
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/arclight.rb
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'arclight/version'
|
|
4
|
+
require 'arclight/engine'
|
|
5
|
+
require 'arclight/repository'
|
|
6
|
+
require 'arclight/year_range'
|
|
2
7
|
|
|
8
|
+
# :nodoc:
|
|
3
9
|
module Arclight
|
|
4
|
-
|
|
10
|
+
autoload :Routes, 'arclight/routes'
|
|
11
|
+
|
|
12
|
+
def self.deprecation
|
|
13
|
+
@deprecation ||= ActiveSupport::Deprecation.new('2.0', 'Arclight')
|
|
14
|
+
end
|
|
5
15
|
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
|
|
5
|
+
module Arclight
|
|
6
|
+
##
|
|
7
|
+
# Arclight install generator
|
|
8
|
+
class Install < Rails::Generators::Base # rubocop:disable Metrics/ClassLength
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
class_option :test, type: :boolean, default: false, aliases: '-t', desc: 'Indicates that app will be installed in a test environment'
|
|
12
|
+
|
|
13
|
+
def create_blacklight_catalog
|
|
14
|
+
remove_file 'app/controllers/catalog_controller.rb'
|
|
15
|
+
copy_file 'catalog_controller.rb', 'app/controllers/catalog_controller.rb'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def include_arclight_solrdocument
|
|
19
|
+
inject_into_file 'app/models/solr_document.rb', after: 'include Blacklight::Solr::Document' do
|
|
20
|
+
"\n include Arclight::SolrDocument"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def install_blacklight_locale_picker
|
|
25
|
+
gem 'blacklight-locale_picker'
|
|
26
|
+
|
|
27
|
+
Bundler.with_unbundled_env do
|
|
28
|
+
run 'bundle install'
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
generate 'blacklight:locale_picker:install'
|
|
32
|
+
|
|
33
|
+
inject_into_file 'app/helpers/application_helper.rb', after: 'include Blacklight::LocalePicker::LocaleHelper' do
|
|
34
|
+
"\n\n def additional_locale_routing_scopes\n [blacklight, arclight_engine]\n end"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add_custom_routes
|
|
39
|
+
inject_into_file 'config/routes.rb', after: "mount Blacklight::Engine => '/'" do
|
|
40
|
+
"\n mount Arclight::Engine => '/'\n"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
gsub_file 'config/routes.rb', 'root to: "catalog#index"', 'root to: "arclight/repositories#index"'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def add_frontend
|
|
47
|
+
if ENV['CI']
|
|
48
|
+
run "yarn add file:#{Arclight::Engine.root}"
|
|
49
|
+
elsif options[:test]
|
|
50
|
+
run 'yarn link arclight'
|
|
51
|
+
|
|
52
|
+
# If a branch was specified (e.g. you are running a template.rb build
|
|
53
|
+
# against a test branch), use the latest version available on npm
|
|
54
|
+
elsif ENV['BRANCH']
|
|
55
|
+
run 'yarn add arclight@latest'
|
|
56
|
+
|
|
57
|
+
# Otherwise, pick the version from npm that matches the Arclight
|
|
58
|
+
# gem version
|
|
59
|
+
else
|
|
60
|
+
run "yarn add arclight@#{arclight_yarn_version}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def add_stylesheets
|
|
65
|
+
copy_file 'arclight.scss', 'app/assets/stylesheets/arclight.scss'
|
|
66
|
+
append_to_file 'app/assets/stylesheets/application.bootstrap.scss' do
|
|
67
|
+
<<~CONTENT
|
|
68
|
+
@import "arclight";
|
|
69
|
+
CONTENT
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def add_arclight_search_behavior
|
|
74
|
+
inject_into_file 'app/models/search_builder.rb', after: 'include Blacklight::Solr::SearchBuilderBehavior' do
|
|
75
|
+
"\n include Arclight::SearchBehavior"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def solr_config
|
|
80
|
+
directory '../../../../solr', 'solr', force: true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def add_repository_config
|
|
84
|
+
copy_file 'config/repositories.yml' unless File.exist?('config/repositories.yml')
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def add_download_config
|
|
88
|
+
copy_file 'config/downloads.yml' unless File.exist?('config/downloads.yml')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def add_i18n_config
|
|
92
|
+
copy_file 'config/locales/arclight.en.yml'
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def modify_blacklight_yml
|
|
96
|
+
gsub_file 'config/locales/blacklight.en.yml', "application_name: 'Blacklight'", "application_name: 'Arclight'"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def assets
|
|
100
|
+
if using_importmap?
|
|
101
|
+
pin_javascript_dependencies
|
|
102
|
+
import_arclight_javascript
|
|
103
|
+
else
|
|
104
|
+
install_javascript_dependencies
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def inject_arclight_routes
|
|
109
|
+
inject_into_file 'config/routes.rb',
|
|
110
|
+
" concern :hierarchy, Arclight::Routes::Hierarchy.new\n",
|
|
111
|
+
after: /concern :exportable.*\n/
|
|
112
|
+
inject_into_file 'config/routes.rb',
|
|
113
|
+
" concerns :hierarchy\n",
|
|
114
|
+
after: /resources :solr_documents.*\n/
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def root
|
|
120
|
+
@root ||= Pathname(destination_root)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def using_importmap?
|
|
124
|
+
@using_importmap ||= root.join('config/importmap.rb').exist?
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# This is the last step because any failure here wouldn't prevent the other steps from running
|
|
128
|
+
def install_javascript_dependencies
|
|
129
|
+
inject_into_file 'app/assets/javascripts/application.js', after: '//= require blacklight/blacklight' do
|
|
130
|
+
"\n// Required by Arclight" \
|
|
131
|
+
"\n//= require arclight/arclight"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def pin_javascript_dependencies
|
|
136
|
+
say 'Arclight Importmap asset generation'
|
|
137
|
+
|
|
138
|
+
append_to_file 'config/importmap.rb', <<~RUBY
|
|
139
|
+
pin "arclight", to: "arclight/arclight.js"
|
|
140
|
+
# TODO: We may be able to move these to a single importmap for arclight.
|
|
141
|
+
pin "arclight/oembed_controller", to: "arclight/oembed_controller.js"
|
|
142
|
+
pin "arclight/truncate_controller", to: "arclight/truncate_controller.js"
|
|
143
|
+
RUBY
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def import_arclight_javascript
|
|
147
|
+
append_to_file 'app/javascript/application.js', "\nimport \"arclight\""
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def package_yarn_version(package_name, requested_version)
|
|
151
|
+
versions = JSON.parse(`yarn info #{package_name} versions --json`)['data']
|
|
152
|
+
exact_match = versions.find { |v| v == requested_version }
|
|
153
|
+
return exact_match if exact_match
|
|
154
|
+
|
|
155
|
+
major_version = Gem::Version.new(requested_version).segments.first
|
|
156
|
+
"^#{major_version}"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def arclight_yarn_version
|
|
160
|
+
package_yarn_version('arclight', Arclight::VERSION)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|