arclight 0.0.1 → 0.1.0

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.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc +12 -0
  3. data/.gitignore +5 -0
  4. data/.rubocop.yml +66 -0
  5. data/.solr_wrapper +5 -0
  6. data/.travis.yml +30 -2
  7. data/CONTRIBUTING.md +43 -0
  8. data/Gemfile +36 -0
  9. data/LICENSE.txt +1 -0
  10. data/README.md +85 -12
  11. data/Rakefile +14 -3
  12. data/app/assets/images/blacklight/compact.svg +25 -0
  13. data/app/assets/images/blacklight/logo.png +0 -0
  14. data/app/assets/javascripts/arclight/arclight.js +9 -0
  15. data/app/assets/javascripts/arclight/collection_context.js +18 -0
  16. data/app/assets/javascripts/arclight/collection_navigation.js +114 -0
  17. data/app/assets/javascripts/arclight/collection_scrollspy.js +6 -0
  18. data/app/assets/javascripts/arclight/component_ancestors.js +56 -0
  19. data/app/assets/javascripts/arclight/oembed_viewer.js +39 -0
  20. data/app/assets/javascripts/arclight/truncator.js.erb +23 -0
  21. data/app/assets/stylesheets/arclight/application.scss +13 -0
  22. data/app/assets/stylesheets/arclight/bootstrap_overrides.scss +3 -0
  23. data/app/assets/stylesheets/arclight/modules/collection_search.scss +3 -0
  24. data/app/assets/stylesheets/arclight/modules/hierarchy_and_online_contents.scss +174 -0
  25. data/app/assets/stylesheets/arclight/modules/highlights.scss +10 -0
  26. data/app/assets/stylesheets/arclight/modules/layout.scss +33 -0
  27. data/app/assets/stylesheets/arclight/modules/mastheads.scss +60 -0
  28. data/app/assets/stylesheets/arclight/modules/repositories.scss +29 -0
  29. data/app/assets/stylesheets/arclight/modules/repository_card.scss +54 -0
  30. data/app/assets/stylesheets/arclight/modules/search_results.scss +75 -0
  31. data/app/assets/stylesheets/arclight/modules/show_collection.scss +78 -0
  32. data/app/assets/stylesheets/arclight/modules/sidebar.scss +16 -0
  33. data/app/assets/stylesheets/arclight/variables.scss +2 -0
  34. data/app/controllers/arclight/repositories_controller.rb +40 -0
  35. data/app/controllers/concerns/arclight/field_config_helpers.rb +86 -0
  36. data/app/factories/blacklight_field_configuration_factory.rb +29 -0
  37. data/app/helpers/arclight_helper.rb +173 -0
  38. data/app/models/arclight/parent.rb +24 -0
  39. data/app/models/arclight/parents.rb +34 -0
  40. data/app/models/arclight/requests/google_form.rb +44 -0
  41. data/app/models/concerns/arclight/catalog.rb +22 -0
  42. data/app/models/concerns/arclight/search_behavior.rb +46 -0
  43. data/app/models/concerns/arclight/solr_document.rb +131 -0
  44. data/app/presenters/arclight/index_presenter.rb +10 -0
  45. data/app/presenters/arclight/show_presenter.rb +27 -0
  46. data/app/views/arclight/.keep +0 -0
  47. data/app/views/arclight/repositories/_in_person_repository.html.erb +19 -0
  48. data/app/views/arclight/repositories/_repository.html.erb +62 -0
  49. data/app/views/arclight/repositories/index.html.erb +4 -0
  50. data/app/views/arclight/repositories/show.html.erb +38 -0
  51. data/app/views/arclight/requests/_google_form.html.erb +11 -0
  52. data/app/views/arclight/viewers/_oembed.html.erb +7 -0
  53. data/app/views/catalog/_arclight_document_index_header.html.erb +13 -0
  54. data/app/views/catalog/_arclight_document_index_header_hierarchy_default.html.erb +0 -0
  55. data/app/views/catalog/_arclight_document_index_header_online_contents_default.html.erb +0 -0
  56. data/app/views/catalog/_arclight_document_show_header.html.erb +15 -0
  57. data/app/views/catalog/_arclight_document_show_header_collection.html.erb +12 -0
  58. data/app/views/catalog/_arclight_index_compact_default.html.erb +15 -0
  59. data/app/views/catalog/_arclight_online_content_indicator.html.erb +5 -0
  60. data/app/views/catalog/_arclight_rangelimit.html.erb +24 -0
  61. data/app/views/catalog/_arclight_viewer_default.html.erb +1 -0
  62. data/app/views/catalog/_collection_contents.html.erb +12 -0
  63. data/app/views/catalog/_collection_count.html.erb +7 -0
  64. data/app/views/catalog/_collection_downloads.html.erb +17 -0
  65. data/app/views/catalog/_collection_online_contents.html.erb +17 -0
  66. data/app/views/catalog/_collection_overview.html.erb +7 -0
  67. data/app/views/catalog/_component_overview.html.erb +46 -0
  68. data/app/views/catalog/_context_card.html.erb +27 -0
  69. data/app/views/catalog/_context_sidebar.html.erb +8 -0
  70. data/app/views/catalog/_custom_metadata.html.erb +16 -0
  71. data/app/views/catalog/_home.html.erb +1 -0
  72. data/app/views/catalog/_index_breadcrumb_default.html.erb +3 -0
  73. data/app/views/catalog/_index_default.html.erb +17 -0
  74. data/app/views/catalog/_index_header.html.erb +7 -0
  75. data/app/views/catalog/_index_header_hierarchy_default.html.erb +42 -0
  76. data/app/views/catalog/_index_header_online_contents_default.html.erb +1 -0
  77. data/app/views/catalog/_index_hierarchy_default.html.erb +28 -0
  78. data/app/views/catalog/_index_online_contents_default.html.erb +6 -0
  79. data/app/views/catalog/_results_histogram.html.erb +10 -0
  80. data/app/views/catalog/_search_results.html.erb +31 -0
  81. data/app/views/catalog/_search_results_repository.html.erb +6 -0
  82. data/app/views/catalog/_search_within_form.html.erb +16 -0
  83. data/app/views/catalog/_show_breadcrumbs_default.html.erb +7 -0
  84. data/app/views/catalog/_show_collection.html.erb +40 -0
  85. data/app/views/catalog/_show_component_sidebar.html.erb +12 -0
  86. data/app/views/catalog/_show_default.html.erb +32 -0
  87. data/app/views/catalog/_show_header.html.erb +5 -0
  88. data/app/views/catalog/_show_sidebar.html.erb +30 -0
  89. data/app/views/catalog/index.html.erb +8 -0
  90. data/app/views/layouts/catalog_result.html.erb +7 -0
  91. data/app/views/shared/_breadcrumbs.html.erb +15 -0
  92. data/app/views/shared/_context_sidebar.html.erb +8 -0
  93. data/app/views/shared/_header_navbar.html.erb +54 -0
  94. data/app/views/shared/_main_menu_links.html.erb +6 -0
  95. data/arclight.gemspec +17 -4
  96. data/bin/rails +13 -0
  97. data/config/locales/arclight.en.yml +65 -0
  98. data/config/routes.rb +6 -0
  99. data/lib/arclight.rb +5 -0
  100. data/lib/arclight/custom_component.rb +98 -0
  101. data/lib/arclight/custom_document.rb +93 -0
  102. data/lib/arclight/digital_object.rb +26 -0
  103. data/lib/arclight/engine.rb +55 -0
  104. data/lib/arclight/exceptions.rb +18 -0
  105. data/lib/arclight/indexer.rb +9 -0
  106. data/lib/arclight/normalized_date.rb +45 -0
  107. data/lib/arclight/normalized_id.rb +25 -0
  108. data/lib/arclight/normalized_title.rb +30 -0
  109. data/lib/arclight/repository.rb +91 -0
  110. data/lib/arclight/shared_indexing_behavior.rb +97 -0
  111. data/lib/arclight/shared_terminology_behavior.rb +65 -0
  112. data/lib/arclight/solr_ead_indexer_ext.rb +159 -0
  113. data/lib/arclight/version.rb +3 -1
  114. data/lib/arclight/viewer.rb +45 -0
  115. data/lib/arclight/viewers/oembed.rb +56 -0
  116. data/lib/arclight/year_range.rb +102 -0
  117. data/lib/generators/arclight/install_generator.rb +63 -0
  118. data/lib/generators/arclight/templates/arclight.js +2 -0
  119. data/lib/generators/arclight/templates/arclight.scss +3 -0
  120. data/lib/generators/arclight/templates/catalog_controller.rb +347 -0
  121. data/lib/generators/arclight/templates/config/downloads.yml +13 -0
  122. data/lib/generators/arclight/templates/config/repositories.yml +42 -0
  123. data/lib/generators/arclight/update_generator.rb +22 -0
  124. data/lib/tasks/index.rake +87 -0
  125. data/package.json +24 -0
  126. data/solr/conf/_rest_managed.json +3 -0
  127. data/solr/conf/admin-extra.html +31 -0
  128. data/solr/conf/elevate.xml +36 -0
  129. data/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
  130. data/solr/conf/protwords.txt +21 -0
  131. data/solr/conf/schema.xml +631 -0
  132. data/solr/conf/scripts.conf +24 -0
  133. data/solr/conf/solrconfig.xml +393 -0
  134. data/solr/conf/spellings.txt +2 -0
  135. data/solr/conf/stopwords.txt +58 -0
  136. data/solr/conf/stopwords_en.txt +58 -0
  137. data/solr/conf/synonyms.txt +31 -0
  138. data/solr/conf/xslt/example.xsl +132 -0
  139. data/solr/conf/xslt/example_atom.xsl +67 -0
  140. data/solr/conf/xslt/example_rss.xsl +66 -0
  141. data/solr/conf/xslt/luke.xsl +337 -0
  142. data/tasks/arclight.rake +68 -0
  143. data/template.rb +15 -0
  144. data/vendor/assets/javascripts/responsiveTruncator.js +69 -0
  145. data/vendor/assets/javascripts/stickyfill.js +480 -0
  146. metadata +301 -6
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # A utility class to normalize dates, typically by joining inclusive and bulk dates
6
+ # e.g., "1990-2000, bulk 1990-1999"
7
+ # @see http://www2.archivists.org/standards/DACS/part_I/chapter_2/4_date
8
+ class NormalizedDate
9
+ # @param [String | Array<String>] `inclusive` from the `unitdate`
10
+ # @param [String] `bulk` from the `unitdate`
11
+ # @param [String] `other` from the `unitdate` when type is not specified
12
+ def initialize(inclusive, bulk = nil, other = nil)
13
+ if inclusive.is_a? Array # of YYYY-YYYY for ranges
14
+ @inclusive = YearRange.new(inclusive.include?('/') ? inclusive : inclusive.map { |v| v.tr('-', '/') }).to_s
15
+ elsif inclusive.present?
16
+ @inclusive = inclusive.strip
17
+ end
18
+ @bulk = bulk.strip if bulk.present?
19
+ @other = other.strip if other.present?
20
+ end
21
+
22
+ # @return [String] the normalized title/date
23
+ def to_s
24
+ normalize
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :inclusive, :bulk, :other
30
+
31
+ # @see http://www2.archivists.org/standards/DACS/part_I/chapter_2/4_date for rules
32
+ def normalize
33
+ if inclusive.present?
34
+ result = inclusive.to_s
35
+ result << ", bulk #{bulk}" if bulk.present?
36
+ elsif other.present?
37
+ result = other.to_s
38
+ else
39
+ result = nil
40
+ end
41
+ return if result.blank?
42
+ result.strip
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # A simple utility class to normalize identifiers
6
+ # to be used around the application for linking
7
+ class NormalizedId
8
+ def initialize(id)
9
+ @id = id
10
+ end
11
+
12
+ def to_s
13
+ normalize
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :id
19
+
20
+ def normalize
21
+ raise Arclight::Exceptions::IDNotFound if id.blank?
22
+ id.strip.tr('.', '-')
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # A utility class to normalize titles, typically by joining
6
+ # the title and date, e.g., "My Title, 1990-2000"
7
+ class NormalizedTitle
8
+ # @param [String] `title` from the `unittitle`
9
+ # @param [String] `date` from the `unitdate`
10
+ def initialize(title, date = nil)
11
+ @title = title.gsub(/\s*,\s*$/, '').strip if title.present?
12
+ @date = date.strip if date.present?
13
+ end
14
+
15
+ # @return [String] the normalized title/date
16
+ def to_s
17
+ normalize
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :title, :date, :default
23
+
24
+ def normalize
25
+ result = [title, date].compact.join(', ')
26
+ raise Arclight::Exceptions::TitleNotFound if result.blank?
27
+ result
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ #
5
+ # Static information about a given repository identified by a unique `slug`
6
+ #
7
+ class Repository
8
+ include ActiveModel::Conversion # for to_partial_path
9
+
10
+ FIELDS = %i[name
11
+ description
12
+ visit_note
13
+ building
14
+ address1
15
+ address2
16
+ city
17
+ state
18
+ zip
19
+ country
20
+ phone
21
+ contact_info
22
+ thumbnail_url
23
+ google_request_url
24
+ google_request_mappings
25
+ collection_count].freeze
26
+
27
+ attr_accessor :slug, *FIELDS
28
+
29
+ # @param [String] `slug` the unique identifier for the repository
30
+ # @param [Hash] `data`
31
+ def initialize(slug, data = {})
32
+ @slug = slug
33
+ FIELDS.each do |field|
34
+ value = data[field.to_s]
35
+ send("#{field}=", value) if value.present?
36
+ end
37
+ end
38
+
39
+ # @return [String] handles the formatting of "city, state zip, country"
40
+ def city_state_zip_country
41
+ state_zip = state
42
+ state_zip += " #{zip}" if zip
43
+ [city, state_zip, country].compact.join(', ')
44
+ end
45
+
46
+ # Load repository information from a YAML file
47
+ #
48
+ # @param [String] `filename`
49
+ # @return [Hash<Slug,Repository>]
50
+ def self.from_yaml(file)
51
+ repos = {}
52
+ data = YAML.safe_load(File.read(file))
53
+ data.keys.each do |slug|
54
+ repos[slug] = new(slug, data[slug])
55
+ end
56
+ repos
57
+ end
58
+
59
+ # Mimics ActiveRecord's `all` behavior
60
+ #
61
+ # @return [Array<Repository>]
62
+ def self.all
63
+ from_yaml(ENV['REPOSITORY_FILE'] || 'config/repositories.yml').values
64
+ end
65
+
66
+ # Mimics ActiveRecord dynamic `find_by` behavior for the slug or name
67
+ #
68
+ # @param [String] `slug` or `name`
69
+ # @return [Repository]
70
+ def self.find_by(slug: nil, name: nil)
71
+ if slug
72
+ all.find { |repo| repo.slug == slug }
73
+ elsif name
74
+ all.find { |repo| repo.name == name }
75
+ else
76
+ raise ArgumentError, 'Requires either slug or name parameters to find_by'
77
+ end
78
+ end
79
+
80
+ # Mimics ActiveRecord dynamic `find_by!` behavior for the slug or name
81
+ #
82
+ # @param [String] `slug` or `name` -- same as `find_by`
83
+ # @return [Repository]
84
+ # @raise [ActiveRecord::RecordNotFound] if cannot find repository
85
+ def self.find_by!(*args)
86
+ repository = find_by(*args)
87
+ raise ActiveRecord::RecordNotFound if repository.blank?
88
+ repository
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # A mixin intended to share indexing behavior between
6
+ # the CustomDocument and CustomComponent classes
7
+ module SharedIndexingBehavior
8
+ # @see http://eadiva.com/2/unitdate/
9
+ # @return [YearRange] all of the years between the given years
10
+ def unitdate_for_range
11
+ range = YearRange.new
12
+ return range if normal_unit_dates.blank?
13
+ range << range.parse_ranges(normal_unit_dates)
14
+ range
15
+ end
16
+
17
+ def subjects_array(elements, parent:)
18
+ xpath_elements = elements.map { |el| "local-name()='#{el}'" }.join(' or ')
19
+ subjects = search("//#{parent}/controlaccess/*[#{xpath_elements}]").to_a
20
+ clean_facets_array(subjects.flatten.map(&:text))
21
+ end
22
+
23
+ def names_array(elements, parent:)
24
+ xpath_elements = elements.map { |el| "local-name()='#{el}'" }.join(' or ')
25
+ names = search("//#{parent}/controlaccess/*[#{xpath_elements}]").to_a
26
+ clean_facets_array(names.flatten.map(&:text))
27
+ end
28
+
29
+ # Return a cleaned array of facets without marc subfields
30
+ #
31
+ # E.g. clean_facets_array(
32
+ # ['FacetValue1 |z FacetValue2','FacetValue3']
33
+ # ) => ['FacetValue1 -- FacetValue2', 'FacetValue3']
34
+ def clean_facets_array(facets_array)
35
+ Array(facets_array).map { |text| fix_subfield_demarcators(text) }.compact.uniq
36
+ end
37
+
38
+ # Replace MARC style subfield demarcators
39
+ #
40
+ # Usage: fix_subfield_demarcators("Subject 1 |z Sub-Subject 2") => "Subject 1 -- Sub-Subject 2"
41
+ def fix_subfield_demarcators(value)
42
+ value.gsub(/\|\w{1}/, '--')
43
+ end
44
+
45
+ # Wrap OM's find_by_xpath for convenience
46
+ def search(path)
47
+ find_by_xpath(path) # rubocop:disable DynamicFindBy
48
+ end
49
+
50
+ # If a repository slug is provided via an environment variable `REPOSITORY_ID`,
51
+ # then use that to lookup the name rather than the parsed out name from the EAD
52
+ # @param [String] `repository` the default repository name
53
+ def repository_as_configured(repository)
54
+ slug = ENV['REPOSITORY_ID']
55
+ if slug.present?
56
+ begin
57
+ Arclight::Repository.find_by(slug: slug).name
58
+ rescue => e
59
+ raise "The repository slug '#{slug}' was given but it is not found in the Repository configuration data: #{e}"
60
+ end
61
+ else
62
+ repository
63
+ end
64
+ end
65
+
66
+ def add_digital_content(prefix:, solr_doc:)
67
+ dao = ng_xml.xpath("#{prefix}/dao").to_a
68
+ return if dao.blank?
69
+ field_name = Solrizer.solr_name('digital_objects', :displayable)
70
+ solr_doc[field_name] = digital_objects(dao)
71
+ end
72
+
73
+ def digital_objects(objects)
74
+ objects.map do |dao|
75
+ label = dao.attributes['title'].try(:value) || dao.xpath('daodesc/p').try(:text)
76
+ href = (dao.attributes['href'] || dao.attributes['xlink:href']).try(:value)
77
+ Arclight::DigitalObject.new(label: label, href: href).to_json
78
+ end
79
+ end
80
+
81
+ def add_date_ranges(solr_doc)
82
+ Solrizer.insert_field(solr_doc, 'date_range', unitdate_for_range.years, :facetable)
83
+ end
84
+
85
+ def add_normalized_title(solr_doc)
86
+ dates = Arclight::NormalizedDate.new(unitdate_inclusive.first, unitdate_bulk.first, unitdate_other.first).to_s
87
+ title = Arclight::NormalizedTitle.new(solr_doc['title_ssm'].try(:first), dates).to_s
88
+ solr_doc['normalized_title_ssm'] = [title]
89
+ solr_doc['normalized_date_ssm'] = [dates]
90
+ title
91
+ end
92
+
93
+ def online_content?
94
+ search('//dao[@href]').present?
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # An extendable mixin intended to share terminology behavior between
6
+ # the CustomDocument and CustomComponent classes
7
+ module SharedTerminologyBehavior
8
+ def add_unitid(t, prefix)
9
+ t.unitid(path: prefix + 'did/unitid', index_as: %i[displayable searchable])
10
+ end
11
+
12
+ def add_extent(t, prefix)
13
+ t.extent(path: prefix + 'did/physdesc/extent', index_as: %i[displayable searchable])
14
+ end
15
+
16
+ # date indexing
17
+ def add_dates(t, prefix)
18
+ t.normal_unit_dates(path: prefix + 'did/unitdate/@normal')
19
+ t.unitdate_bulk(path: prefix + 'did/unitdate[@type=\'bulk\']', index_as: %i[displayable])
20
+ t.unitdate_inclusive(path: prefix + 'did/unitdate[@type=\'inclusive\']', index_as: %i[displayable])
21
+ t.unitdate_other(path: prefix + 'did/unitdate[not(@type)]', index_as: %i[displayable])
22
+ t.unitdate(path: prefix + 'did/unitdate', index_as: %i[displayable])
23
+ end
24
+
25
+ def add_searchable_notes(t, prefix) # rubocop: disable Metrics/MethodLength
26
+ # various searchable notes
27
+ %i[
28
+ accessrestrict
29
+ accruals
30
+ acqinfo
31
+ altformavail
32
+ appraisal
33
+ arrangement
34
+ bibliography
35
+ bioghist
36
+ custodhist
37
+ fileplan
38
+ note
39
+ odd
40
+ originalsloc
41
+ otherfindaid
42
+ phystech
43
+ prefercite
44
+ processinfo
45
+ relatedmaterial
46
+ scopecontent
47
+ separatedmaterial
48
+ userestrict
49
+ ].each do |k|
50
+ # many of the notes support various markup so we want everything but the heading
51
+ t.send(k, path: "#{prefix}#{k}/*[local-name()!=\"head\"]", index_as: %i[displayable searchable])
52
+ end
53
+
54
+ # various searchable notes in the did
55
+ %i[
56
+ abstract
57
+ materialspec
58
+ physloc
59
+ ].each do |k|
60
+ t.send(k, path: "#{prefix}did/#{k}", index_as: %i[displayable searchable])
61
+ end
62
+ t.did_note(path: "#{prefix}did/note", index_as: %i[displayable searchable]) # conflicts with top-level note
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arclight
4
+ ##
5
+ # An module to extend SolrEad::Indexer behaviors to allow us to add
6
+ # or override behaviors that require knowledge of the entire XML document.
7
+ module SolrEadIndexerExt
8
+ def additional_component_fields(node, addl_fields = {})
9
+ solr_doc = super
10
+
11
+ add_count_of_child_compontents(node, solr_doc)
12
+ add_ancestral_titles(node, solr_doc)
13
+ add_ancestral_ids(node, solr_doc)
14
+
15
+ add_collection_creator_to_component(node, solr_doc)
16
+
17
+ add_self_or_parents_restrictions(node, solr_doc)
18
+
19
+ add_self_or_parents_terms(node, solr_doc)
20
+
21
+ solr_doc
22
+ end
23
+
24
+ def delete_all
25
+ solr.delete_by_query('*:*')
26
+ solr.commit
27
+ end
28
+
29
+ private
30
+
31
+ # Note that we need to redo what solr_ead does for ids due to our normalization process
32
+ def add_ancestral_ids(node, solr_doc)
33
+ @parent_id_name ||= Solrizer.solr_name('parent', :stored_sortable)
34
+ @parent_ids_field_name ||= Solrizer.solr_name('parent', :displayable)
35
+ @parent_ids_search_field_name ||= Solrizer.solr_name('parent', :searchable)
36
+
37
+ ids = ancestral_ids(node)
38
+ solr_doc[@parent_ids_field_name] = ids
39
+ solr_doc[@parent_ids_search_field_name] = ids
40
+ solr_doc[@parent_id_name] = ids.last
41
+ end
42
+
43
+ # Note that we need to redo what solr_ead does for titles due to our normalization process
44
+ def add_ancestral_titles(node, solr_doc)
45
+ @parent_titles_field_name ||= Solrizer.solr_name('parent_unittitles', :displayable)
46
+ @parent_titles_search_field_name ||= Solrizer.solr_name('parent_unittitles', :searchable)
47
+ @collection_facet_name ||= Solrizer.solr_name('collection', :facetable)
48
+ @collection_name ||= Solrizer.solr_name('collection', :displayable)
49
+
50
+ titles = ancestral_titles(node)
51
+ solr_doc[@parent_titles_field_name] = titles
52
+ solr_doc[@parent_titles_search_field_name] = titles
53
+ solr_doc[@collection_name] = [titles.first] # collection is always on top
54
+ solr_doc[@collection_facet_name] = [titles.first]
55
+ end
56
+
57
+ def add_count_of_child_compontents(node, solr_doc)
58
+ @child_component_count_name ||= Solrizer.solr_name('child_component_count', type: :integer)
59
+
60
+ solr_doc[@child_component_count_name] = node.xpath('count(c)').to_i
61
+ end
62
+
63
+ def ancestral_ids(node)
64
+ ancestral_visit(node, :normalized_component_id, :normalized_collection_id)
65
+ end
66
+
67
+ def ancestral_titles(node)
68
+ ancestral_visit(node, :normalized_component_title, :normalized_collection_title)
69
+ end
70
+
71
+ # visit each component's parent and finish with a visit on the collection
72
+ def ancestral_visit(node, component_fn, collection_fn, results = [])
73
+ while node.parent && node.parent.name == 'c'
74
+ parent = node.parent
75
+ results << send(component_fn, parent)
76
+ node = parent
77
+ end
78
+ results << send(collection_fn, node)
79
+ results.reverse
80
+ end
81
+
82
+ def normalized_component_title(node)
83
+ data = extract_title_and_dates(node)
84
+ normalize_title(data)
85
+ end
86
+
87
+ def normalized_collection_title(node)
88
+ data = extract_title_and_dates(node, '//archdesc/')
89
+ normalize_title(data)
90
+ end
91
+
92
+ def normalize_title(data)
93
+ Arclight::NormalizedTitle.new(
94
+ data[:title],
95
+ Arclight::NormalizedDate.new(
96
+ data[:unitdate_inclusive],
97
+ data[:unitdate_bulk],
98
+ data[:unitdate_other]
99
+ ).to_s
100
+ ).to_s
101
+ end
102
+
103
+ # TODO: these xpaths should be DRY'd up -- they're in both terminologies
104
+ def extract_title_and_dates(node, prefix = nil)
105
+ data = {
106
+ title: node.at_xpath("#{prefix}did/unittitle"),
107
+ unitdate_inclusive: node.at_xpath("#{prefix}did/unitdate[@type=\"inclusive\"]"),
108
+ unitdate_bulk: node.at_xpath("#{prefix}did/unitdate[@type=\"bulk\"]"),
109
+ unitdate_other: node.at_xpath("#{prefix}did/unitdate[not(@type)]")
110
+ }
111
+ data.each do |k, v|
112
+ data[k] = v.text if v
113
+ end
114
+ data
115
+ end
116
+
117
+ def normalized_component_id(node)
118
+ Arclight::NormalizedId.new(node['id'].to_s).to_s
119
+ end
120
+
121
+ def normalized_collection_id(node)
122
+ Arclight::NormalizedId.new(node.document.at_xpath('//eadid').text).to_s
123
+ end
124
+
125
+ # This mimics similar behavior in Arclight::CustomDocument
126
+ def add_collection_creator_to_component(node, solr_doc)
127
+ field_name = Solrizer.solr_name('collection_creator', :displayable)
128
+ repository = solr_doc[Solrizer.solr_name('repository', :displayable)]
129
+ creators = node.xpath('//archdesc/did/origination[@label="creator"]/*/text()').map(&:text)
130
+ solr_doc[field_name] = creators - [repository]
131
+ end
132
+
133
+ def parent_check_list(node, root_path, element_path, results = [])
134
+ orginal_node = node
135
+ results = node.xpath("#{root_path}/#{element_path}").map(&:text)
136
+ # if current restriction return, else go up to parent and check
137
+ while node.parent.name == 'c' && results.blank?
138
+ parent = node.parent
139
+ results = parent.xpath("#{root_path}/#{element_path}").map(&:text)
140
+ node = parent
141
+ end
142
+ # If no parental results, check the collection
143
+ results = orginal_node.xpath("//archdesc/#{element_path}").map(&:text) if results.blank?
144
+ results.flatten # can't use with flatten! because that returns nil
145
+ end
146
+
147
+ def add_self_or_parents_restrictions(node, solr_doc)
148
+ field_name = Solrizer.solr_name('parent_access_restrict', :displayable)
149
+ solr_doc[field_name] = parent_check_list(node, './', 'accessrestrict/p/text()')
150
+ solr_doc[field_name]
151
+ end
152
+
153
+ def add_self_or_parents_terms(node, solr_doc)
154
+ field_name = Solrizer.solr_name('parent_access_terms', :displayable)
155
+ solr_doc[field_name] = parent_check_list(node, './', 'userestrict/p/text()')
156
+ solr_doc[field_name]
157
+ end
158
+ end
159
+ end