mods_display 0.5.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d059d0bb717d798e323ae91d62ac65420e0b7e34
4
- data.tar.gz: bea28312f2984465139f312e585a315ec8de992f
2
+ SHA256:
3
+ metadata.gz: 3ac207676bdeba25c5fe05931498d9404d37d3417f2eac034f0dba58f4bbabbd
4
+ data.tar.gz: 8a1de09dadf358278f6268a41a1e858957c45acdf9fdd5acd5c5c825a391c710
5
5
  SHA512:
6
- metadata.gz: a7f53d54b8fbbd9dee47ccbee7120e63725e63057602da3a3cd50f569fc1d3335ed38a326d0ae072a25457f207d4ccc7722aa79a28dab3f442b2bf78f81ea35a
7
- data.tar.gz: 306dee0b66098f941dcfbb8c8f8138b001cf4f34b06b398b73a118c9f785b73c9ebab33f9e7b0233aa7cb6d8122034a0bc77f87ba355d5bc29fa45046ba6dca6
6
+ metadata.gz: 84317fe034efa431a52105abd35529992a657e331b8f42f4571b8fc0369c82297a87b7dbb6c0156cd16e4708aa3232c133106f66f55563898a300faf6f10e6a2
7
+ data.tar.gz: 22f10cbd6bca1feb6796a7a3b2be2e7620f6b25ea285395f075446a8571c1b32f53e9c3f58310b8ac48812d4c31eef72344332950b86ece74cc56f86d395577b
@@ -0,0 +1,24 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ tests:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ ruby: [2.6, 2.7]
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - name: Set up Ruby
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: ${{ matrix.ruby }}
21
+ - name: Install dependencies
22
+ run: bundle install
23
+ - name: Run tests
24
+ run: bundle exec rake
data/Gemfile CHANGED
@@ -3,5 +3,3 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in mods_display.gemspec
4
4
  gemspec
5
5
 
6
- # Pin to activesupport 4.x for older versions of ruby
7
- gem 'activesupport', '~> 4.2' if RUBY_VERSION < '2.2.2'
data/lib/mods_display.rb CHANGED
@@ -49,3 +49,14 @@ require 'i18n/backend/fallbacks'
49
49
  I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
50
50
  I18n.load_path += Dir["#{File.expand_path('../..', __FILE__)}/config/locales/*.yml"]
51
51
  I18n.backend.load_translations
52
+
53
+ # load Rails/Railtie
54
+ begin
55
+ require 'rails'
56
+ rescue LoadError
57
+ #do nothing
58
+ end
59
+
60
+ if defined? ::Rails::Railtie
61
+ require 'mods_display/railtie'
62
+ end
@@ -1,5 +1,53 @@
1
1
  module ModsDisplay
2
2
  class AccessCondition < Field
3
+ LICENSES = {
4
+ 'cc-none' => { desc: '' },
5
+ 'cc-by' => {
6
+ link: 'http://creativecommons.org/licenses/by/3.0/',
7
+ desc: 'This work is licensed under a Creative Commons Attribution 3.0 Unported License'
8
+ },
9
+ 'cc-by-sa' => {
10
+ link: 'http://creativecommons.org/licenses/by-sa/3.0/',
11
+ desc: 'This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License'
12
+ },
13
+ 'cc-by-nd' => {
14
+ link: 'http://creativecommons.org/licenses/by-nd/3.0/',
15
+ desc: 'This work is licensed under a Creative Commons Attribution-No Derivative Works 3.0 Unported License'
16
+ },
17
+ 'cc-by-nc' => {
18
+ link: 'http://creativecommons.org/licenses/by-nc/3.0/',
19
+ desc: 'This work is licensed under a Creative Commons Attribution-Noncommercial 3.0 Unported License'
20
+ },
21
+ 'cc-by-nc-sa' => {
22
+ link: 'http://creativecommons.org/licenses/by-nc-sa/3.0/',
23
+ desc: 'This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License'
24
+ },
25
+ 'cc-by-nc-nd' => {
26
+ link: 'http://creativecommons.org/licenses/by-nc-nd/3.0/',
27
+ desc: 'This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License'
28
+ },
29
+ 'cc-pdm' => {
30
+ link: 'http://creativecommons.org/publicdomain/mark/1.0/',
31
+ desc: 'This work is in the public domain per Creative Commons Public Domain Mark 1.0'
32
+ },
33
+ 'odc-odc-pddl' => {
34
+ link: 'http://opendatacommons.org/licenses/pddl/',
35
+ desc: 'This work is licensed under a Open Data Commons Public Domain Dedication and License (PDDL)'
36
+ },
37
+ 'odc-pddl' => {
38
+ link: 'http://opendatacommons.org/licenses/pddl/',
39
+ desc: 'This work is licensed under a Open Data Commons Public Domain Dedication and License (PDDL)'
40
+ },
41
+ 'odc-odc-by' => {
42
+ link: 'http://opendatacommons.org/licenses/by/',
43
+ desc: 'This work is licensed under a Open Data Commons Attribution License'
44
+ },
45
+ 'odc-odc-odbl' => {
46
+ link: 'http://opendatacommons.org/licenses/odbl/',
47
+ desc: 'This work is licensed under a Open Data Commons Open Database License (ODbL)'
48
+ }
49
+ }.freeze
50
+
3
51
  def fields
4
52
  return_fields = @values.map do |value|
5
53
  ModsDisplay::Values.new(
@@ -28,26 +76,32 @@ module ModsDisplay
28
76
  end
29
77
 
30
78
  def license_statement(element)
31
- element.text[/^(.*) (.*):(.*)$/]
32
- output = "<div class='#{[Regexp.last_match(1), Regexp.last_match(2)].join('-').downcase}'>"
33
- if license_link(Regexp.last_match(1), Regexp.last_match(2))
34
- link = "<a href='#{license_link(Regexp.last_match(1), Regexp.last_match(2))}'>#{Regexp.last_match(3).strip}</a>"
35
- output << link
36
- else
37
- output << Regexp.last_match(3).strip
38
- end
79
+ matches = element.text.match(/^(?<code>.*) (?<type>.*):(?<description>.*)$/)
80
+ code = matches[:code].downcase
81
+ type = matches[:type].downcase
82
+ description = license_description(code, type) || matches[:description]
83
+ url = license_url(code, type)
84
+ output = "<div class='#{code}-#{type}'>"
85
+ output << if url
86
+ "<a href='#{url}'>#{description}</a>"
87
+ else
88
+ description
89
+ end
39
90
  output << '</div>'
40
91
  end
41
92
 
42
- def license_code_urls
43
- { 'cc' => 'http://creativecommons.org/licenses/',
44
- 'odc' => 'http://opendatacommons.org/licenses/' }
93
+ def license_url(code, type)
94
+ key = "#{code}-#{type}"
95
+ return unless LICENSES.key?(key)
96
+
97
+ LICENSES[key][:link]
45
98
  end
46
99
 
47
- def license_link(code, type)
48
- code = code.downcase
49
- return unless license_code_urls.key?(code)
50
- "#{license_code_urls[code]}#{type.downcase}#{"/#{@config.cc_license_version}/" if code == 'cc'}"
100
+ def license_description(code, type)
101
+ key = "#{code}-#{type}"
102
+ return unless LICENSES.key?(key) && LICENSES[key][:desc]
103
+
104
+ LICENSES[key][:desc]
51
105
  end
52
106
 
53
107
  def access_label(element)
@@ -13,7 +13,7 @@ module ModsDisplay
13
13
  private
14
14
 
15
15
  def extent_fields
16
- @values.map(&:extent)
16
+ @values.map(&:extent).flatten
17
17
  end
18
18
  end
19
19
  end
@@ -5,7 +5,7 @@ module ModsDisplay
5
5
  [
6
6
  ModsDisplay::Values.new(
7
7
  label: I18n.t('mods_display.form'),
8
- values: form_fields.map(&:text)
8
+ values: form_fields.map(&:text).uniq { |x| x.downcase.gsub(/\s/, '').gsub(/[[:punct:]]/, '') }
9
9
  )
10
10
  ]
11
11
  end
@@ -13,7 +13,7 @@ module ModsDisplay
13
13
  private
14
14
 
15
15
  def form_fields
16
- @values.map(&:form)
16
+ @values.map(&:form).flatten
17
17
  end
18
18
  end
19
19
  end
@@ -68,6 +68,8 @@ module ModsDisplay
68
68
  date_fields.map do |date_field|
69
69
  if date_is_w3cdtf?(date_field)
70
70
  process_w3cdtf_date(date_field)
71
+ elsif date_is_iso8601?(date_field)
72
+ process_iso8601_date(date_field)
71
73
  else
72
74
  date_field
73
75
  end
@@ -158,6 +160,10 @@ module ModsDisplay
158
160
  field_is_encoded?(date_field, 'w3cdtf')
159
161
  end
160
162
 
163
+ def date_is_iso8601?(date_field)
164
+ field_is_encoded?(date_field, 'iso8601')
165
+ end
166
+
161
167
  def process_w3cdtf_date(date_field)
162
168
  date_field = date_field.clone
163
169
  date_field.content = begin
@@ -174,6 +180,16 @@ module ModsDisplay
174
180
  date_field
175
181
  end
176
182
 
183
+ def process_iso8601_date(date_field)
184
+ date_field = date_field.clone
185
+ date_field.content = begin
186
+ Date.iso8601(date_field.text).strftime(@config.full_date_format)
187
+ rescue
188
+ date_field.content
189
+ end
190
+ date_field
191
+ end
192
+
177
193
  def dedup_dates(date_fields)
178
194
  date_text = date_fields.map { |d| normalize_date(d.text) }
179
195
  if date_text != date_text.uniq
@@ -7,30 +7,34 @@ module ModsDisplay
7
7
  include ModsDisplay::RelatedItemConcerns
8
8
 
9
9
  def fields
10
- return_fields = @values.map do |value|
11
- next if related_item_is_a_collection?(value)
12
- next unless render_nested_related_item?(value)
10
+ @fields ||= begin
11
+ return_fields = @values.map do |value|
12
+ next if related_item_is_a_collection?(value)
13
+ next unless render_nested_related_item?(value)
13
14
 
14
- related_item_mods_object(value)
15
- end.compact
16
- collapse_fields(return_fields)
15
+ related_item_mods_object(value)
16
+ end.compact
17
+ collapse_fields(return_fields)
18
+ end
17
19
  end
18
20
 
19
21
  def to_html
20
- return nil if fields.empty? || @config.ignore?
21
- output = ''
22
- fields.each do |field|
23
- next unless field.values.any? { |f| f && !f.empty? }
24
- output << "<dt#{label_class} #{sanitized_field_title(field.label)}>#{field.label}</dt>"
25
- output << "<dd#{value_class}>"
26
- output << '<ul class="mods_display_nested_related_items">'
27
- output << field.values.map do |val|
28
- "<li class='mods_display_nested_related_item open'>#{link_urls_and_email(val.to_s)}</li>"
29
- end.join
30
- output << '</ul>'
31
- output << '</dd>'
22
+ return if fields.empty? || @config.ignore?
23
+ @to_html ||= begin
24
+ output = ''
25
+ fields.each do |field|
26
+ next unless field.values.any? { |f| f && !f.empty? }
27
+ output << "<dt#{label_class} #{sanitized_field_title(field.label)}>#{field.label}</dt>"
28
+ output << "<dd#{value_class}>"
29
+ output << '<ul class="mods_display_nested_related_items">'
30
+ output << field.values.map do |val|
31
+ "<li class='mods_display_nested_related_item open'>#{link_urls_and_email(val.to_s)}</li>"
32
+ end.join
33
+ output << '</ul>'
34
+ output << '</dd>'
35
+ end
36
+ output
32
37
  end
33
- output
34
38
  end
35
39
 
36
40
  private
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ module Helpers
5
+ module RecordHelper
6
+ def display_content_field(field)
7
+ return unless field.respond_to?(:label, :values) && field.values.any?(&:present?)
8
+
9
+ display_content_label(field.label) + display_content_values(field.values)
10
+ end
11
+
12
+ def display_content_label(label)
13
+ content_tag :dt, label
14
+ end
15
+
16
+ def display_content_values(values)
17
+ values.map do |value|
18
+ content_tag :dd, value
19
+ end.join('').html_safe
20
+ end
21
+
22
+ def mods_display_label(label)
23
+ content_tag(:dt, label.delete(':')) + "\n".html_safe
24
+ end
25
+
26
+ def mods_display_content(values, delimiter = nil)
27
+ if delimiter
28
+ content_tag(:dd, values.map do |value|
29
+ link_urls_and_email(value) if value.present?
30
+ end.compact.join(delimiter).html_safe)
31
+ else
32
+ Array[values].flatten.map do |value|
33
+ content_tag(:dd, link_urls_and_email(value.to_s).html_safe) if value.present?
34
+ end.join.html_safe
35
+ end
36
+ end
37
+
38
+ def mods_record_field(field, delimiter = nil)
39
+ return unless field.respond_to?(:label, :values) && field.values.any?(&:present?)
40
+
41
+ mods_display_label(field.label) + mods_display_content(field.values, delimiter)
42
+ end
43
+
44
+ def mods_name_field(field, &block)
45
+ return unless field.respond_to?(:label, :values) && field.values.any?(&:present?)
46
+
47
+ mods_display_label(field.label) + mods_display_name(field.values, &block)
48
+ end
49
+
50
+ def mods_display_name(names, &block)
51
+ names.map do |name|
52
+ content_tag(:dd) do
53
+ block_given? ? yield(name.name) : name.name
54
+ end
55
+ end.join.html_safe
56
+ end
57
+
58
+ # We need this to remove the ending ":" from the role labels only in data from
59
+ # mods_display
60
+ def sanitize_mods_name_label(label)
61
+ label.sub(/:$/, '')
62
+ end
63
+
64
+ def mods_subject_field(subject, &block)
65
+ return unless subject.values.any?(&:present?)
66
+
67
+ fields = subject.values.map do |subject_line|
68
+ content_tag :dd, safe_join(link_mods_subjects(subject_line, &block), ' > ')
69
+ end
70
+
71
+ (mods_display_label(subject.label) + safe_join(fields))
72
+ end
73
+
74
+ def mods_genre_field(genre, &block)
75
+ return unless genre.values.any?(&:present?)
76
+
77
+ fields = genre.values.map do |genre_line|
78
+ content_tag :dd, link_mods_genres(genre_line, &block)
79
+ end
80
+
81
+ mods_display_label(genre.label) + safe_join(fields)
82
+ end
83
+
84
+ def link_mods_genres(genre, &block)
85
+ link_buffer = []
86
+ link_to_mods_subject(genre, link_buffer, &block)
87
+ end
88
+
89
+ def link_mods_subjects(subjects, &block)
90
+ link_buffer = []
91
+ linked_subjects = []
92
+ subjects.each do |subject|
93
+ if subject.present?
94
+ linked_subjects << link_to_mods_subject(subject, link_buffer, &block)
95
+ end
96
+ end
97
+ linked_subjects
98
+ end
99
+
100
+ def link_to_mods_subject(subject, buffer, &block)
101
+ subject_text = subject.respond_to?(:name) ? subject.name : subject
102
+ link = block_given? ? yield(subject_text, buffer) : subject_text
103
+ buffer << subject_text.strip
104
+ link << " (#{subject.roles.join(', ')})" if subject.respond_to?(:roles) && subject.roles.present?
105
+ link
106
+ end
107
+
108
+ # rubocop:disable Layout/LineLength
109
+ def link_urls_and_email(val)
110
+ val = val.dup
111
+ # http://daringfireball.net/2010/07/improved_regex_for_matching_urls
112
+ url = %r{(?i)\b(?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\([^\s()<>]+|\([^\s()<>]+\)*\))+(?:\([^\s()<>]+|\([^\s()<>]+\)*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’])}i
113
+ # http://www.regular-expressions.info/email.html
114
+ email = %r{[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|asia|jobs|museum)\b}i
115
+ matches = [val.scan(url), val.scan(email)].flatten.uniq
116
+ unless val =~ /<a/ # we'll assume that linking has alraedy occured and we don't want to double link
117
+ matches.each do |match|
118
+ if match =~ email
119
+ val.gsub!(match, "<a href='mailto:#{match}'>#{match}</a>")
120
+ else
121
+ val.gsub!(match, "<a href='#{match}'>#{match}</a>")
122
+ end
123
+ end
124
+ end
125
+ val
126
+ end
127
+ # rubocop:enable Layout/LineLength
128
+
129
+ end
130
+ end
131
+ end
@@ -1,6 +1,5 @@
1
1
  module ModsDisplay
2
2
  class HTML
3
- attr_reader :title, :body
4
3
  def initialize(config, xml, klass)
5
4
  @config = config
6
5
  @stanford_mods = xml
@@ -24,9 +23,7 @@ module ModsDisplay
24
23
  body_fields[0] = :subTitle
25
24
  body_fields.each do |field_key|
26
25
  field = mods_field(@xml, field_key)
27
- unless field.nil? || field.to_html.nil?
28
- output << mods_field(@xml, field_key).to_html
29
- end
26
+ output << field.to_html unless field.nil? || field.to_html.nil?
30
27
  end
31
28
  output << '</dl>'
32
29
  end
@@ -43,7 +40,7 @@ module ModsDisplay
43
40
  def method_missing(method_name, *args, &block)
44
41
  if to_s.respond_to?(method_name)
45
42
  to_html.send(method_name, *args, &block)
46
- elsif mods_display_fields.include?(method_name)
43
+ elsif method_name == :subTitle || mods_display_fields.include?(method_name)
47
44
  mods_field(@xml, method_name).fields
48
45
  else
49
46
  super
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class Railtie < ::Rails::Railtie
5
+ ActiveSupport.on_load :action_view do
6
+ require 'mods_display/helpers/record_helper'
7
+ ::ActionView::Base.send :include, ModsDisplay::Helpers::RecordHelper
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module ModsDisplay
2
- VERSION = '0.5.0'
2
+ VERSION = '0.8.0'
3
3
  end
data/mods_display.gemspec CHANGED
@@ -22,7 +22,9 @@ Gem::Specification.new do |gem|
22
22
 
23
23
  gem.add_development_dependency 'rake'
24
24
  gem.add_development_dependency 'rspec', '~> 3.0'
25
+ gem.add_development_dependency 'rspec-rails'
25
26
  gem.add_development_dependency 'rubocop'
26
27
  gem.add_development_dependency 'capybara'
27
28
  gem.add_development_dependency 'byebug'
29
+ gem.add_development_dependency 'rails', '~> 6.0'
28
30
  end
data/spec/fake_app.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_controller/railtie'
4
+ require 'action_view/railtie'
5
+
6
+ class ModsDisplayTestApp < Rails::Application
7
+ end
8
+
9
+ Rails.application.routes.draw do
10
+ resources 'searches', only: :index
11
+ end
12
+
13
+ class ApplicationController < ActionController::Base; end
14
+ class SearchesController < ApplicationController
15
+ def index; end
16
+ end
17
+
18
+ Object.const_set(:ApplicationHelper, Module.new)
@@ -8,14 +8,6 @@ def mods_display_access_condition(mods_record)
8
8
  )
9
9
  end
10
10
 
11
- def mods_display_versioned_access_condition(mods_record, version)
12
- ModsDisplay::AccessCondition.new(
13
- mods_record,
14
- ModsDisplay::Configuration::AccessCondition.new { cc_license_version version },
15
- double('controller')
16
- )
17
- end
18
-
19
11
  def mods_display_non_ignore_access_condition(mods_record)
20
12
  ModsDisplay::AccessCondition.new(
21
13
  mods_record,
@@ -68,31 +60,19 @@ describe ModsDisplay::AccessCondition do
68
60
  expect(fields.first.values.length).to eq(1)
69
61
  expect(fields.first.values.first).to include("<a href='http://creativecommons.org/licenses/by-sa/3.0/'>")
70
62
  expect(fields.first.values.first).to include(
71
- 'This work is licensed under a Creative Commons Attribution-Noncommercial 3.0 Unported License'
63
+ 'This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License'
72
64
  )
73
65
  end
74
66
  it 'should itentify and link OpenDataCommons licenses properly' do
75
67
  fields = mods_display_access_condition(@odc_license_note).fields
76
68
  expect(fields.length).to eq(1)
77
69
  expect(fields.first.values.length).to eq(1)
78
- expect(fields.first.values.first).to include("<a href='http://opendatacommons.org/licenses/pddl'>")
70
+ expect(fields.first.values.first).to include("<a href='http://opendatacommons.org/licenses/pddl/'>")
79
71
  expect(fields.first.values.first).to include(
80
72
  'This work is licensed under a Open Data Commons Public Domain Dedication and License (PDDL)'
81
73
  )
82
74
  end
83
- it 'should have a configurable version for CC licenses' do
84
- fields = mods_display_versioned_access_condition(@cc_license_note, '4.0').fields
85
- expect(fields.length).to eq(1)
86
- expect(fields.first.values.length).to eq(1)
87
- expect(fields.first.values.first).to include('http://creativecommons.org/licenses/by-sa/4.0/')
88
- expect(fields.first.values.first).not_to include('http://creativecommons.org/licenses/by-sa/3.0/')
89
- end
90
- it 'should not apply configured version to NON-CC licenses' do
91
- fields = mods_display_versioned_access_condition(@odc_license_note, '4.0').fields
92
- expect(fields.length).to eq(1)
93
- expect(fields.first.values.length).to eq(1)
94
- expect(fields.first.values.first).not_to include('/4.0/')
95
- end
75
+
96
76
  it 'should not attempt unknown license types' do
97
77
  fields = mods_display_access_condition(@no_link_license_note).fields
98
78
  expect(fields.length).to eq(1)
@@ -6,6 +6,7 @@ describe ModsDisplay::Extent do
6
6
  <mods>
7
7
  <physicalDescription>
8
8
  <extent>Extent Value</extent>
9
+ <extent>Extent Value 2</extent>
9
10
  </physicalDescription>
10
11
  </mods>
11
12
  XML
@@ -24,7 +25,7 @@ describe ModsDisplay::Extent do
24
25
 
25
26
  describe 'values' do
26
27
  it 'returns the text of the extent element' do
27
- expect(subject.first.values).to eq ['Extent Value']
28
+ expect(subject.first.values).to eq ['Extent Value', 'Extent Value 2']
28
29
  end
29
30
  end
30
31
  end
@@ -6,6 +6,7 @@ describe ModsDisplay::Form do
6
6
  <mods>
7
7
  <physicalDescription>
8
8
  <form>Form Value</form>
9
+ <form>Form Value 2</form>
9
10
  </physicalDescription>
10
11
  </mods>
11
12
  XML
@@ -24,7 +25,25 @@ describe ModsDisplay::Form do
24
25
 
25
26
  describe 'values' do
26
27
  it 'returns the text of the form element' do
27
- expect(subject.first.values).to eq ['Form Value']
28
+ expect(subject.first.values).to eq ['Form Value', 'Form Value 2']
29
+ end
30
+
31
+ context 'duplicated data' do
32
+ let(:mods) do
33
+ <<-XML
34
+ <mods>
35
+ <physicalDescription>
36
+ <form authority="gmd">electronic resource.</form>
37
+ <form authority="zxy">electronicresource!</form>
38
+ <form authority="marccategory">electronic Resource</form>
39
+ </physicalDescription>
40
+ </mods>
41
+ XML
42
+ end
43
+
44
+ it 'uses only unique form values, ignore differences in case, punctuation or whitespace' do
45
+ expect(subject.first.values).to match_array ['electronic resource.']
46
+ end
28
47
  end
29
48
  end
30
49
  end
@@ -43,6 +43,7 @@ describe ModsDisplay::Imprint do
43
43
  @imprint_date_range = Stanford::Mods::Record.new.from_str(imprint_date_range, false).origin_info
44
44
  @encoded_place = Stanford::Mods::Record.new.from_str(encoded_place, false).origin_info
45
45
  @encoded_dates = Stanford::Mods::Record.new.from_str(encoded_dates, false).origin_info
46
+ @iso8601_encoded_dates = Stanford::Mods::Record.new.from_str(iso8601_encoded_dates, false).origin_info
46
47
  @bad_dates = Stanford::Mods::Record.new.from_str(bad_dates, false).origin_info
47
48
  @invalid_dates = Stanford::Mods::Record.new.from_str(invalid_dates, false).origin_info
48
49
  @punctuation_imprint = Stanford::Mods::Record.new.from_str(punctuation_imprint_fixture, false).origin_info
@@ -209,6 +210,23 @@ describe ModsDisplay::Imprint do
209
210
  end.values).to eq(['July (2013)'])
210
211
  end
211
212
  end
213
+
214
+ describe 'iso8601' do
215
+ it 'handles full dates properly' do
216
+ fields = mods_display_imprint(@iso8601_encoded_dates).fields
217
+ expect(fields.length).to eq(2)
218
+ expect(fields.find do |field|
219
+ field.label == 'Date created:'
220
+ end.values).to eq(['November 14, 2013'])
221
+ end
222
+ it "should not try to handle dates we can't parse" do
223
+ fields = mods_display_imprint(@iso8601_encoded_dates).fields
224
+ expect(fields.length).to eq(2)
225
+ expect(fields.find do |field|
226
+ field.label == 'Date modified:'
227
+ end.values).to eq(['Jul. 22, 2013'])
228
+ end
229
+ end
212
230
  end
213
231
  describe 'bad dates' do
214
232
  it 'should ignore date values' do
@@ -29,11 +29,18 @@ describe ModsDisplay::NestedRelatedItem do
29
29
  end
30
30
 
31
31
  describe '#fields' do
32
+ let(:mods) { multi_constituent_fixture }
32
33
  subject(:fields) { nested_related_item.fields }
33
34
 
34
- context 'when an element that should be nested' do
35
- let(:mods) { multi_constituent_fixture }
35
+ describe 'memoization' do
36
+ it 'only calls related_item_mods_object once per item regardless of how many times the method is called' do
37
+ expect(nested_related_item).to receive(:related_item_mods_object).exactly(2).times
38
+
39
+ 5.times { nested_related_item.fields }
40
+ end
41
+ end
36
42
 
43
+ context 'when an element that should be nested' do
37
44
  it 'has a field for each related item' do
38
45
  expect(fields.length).to eq 1
39
46
  end
@@ -59,6 +66,16 @@ describe ModsDisplay::NestedRelatedItem do
59
66
  subject(:html) { Capybara.string(nested_related_item.to_html) }
60
67
  let(:mods) { related_item_host_fixture }
61
68
 
69
+ describe 'memoization' do
70
+ let(:mods) { multi_constituent_fixture }
71
+
72
+ it 'only loops throug hthe fields once regardless of how many times the method is called' do
73
+ expect(nested_related_item.fields).to receive(:each).exactly(1).times.and_call_original
74
+
75
+ 5.times { nested_related_item.to_html }
76
+ end
77
+ end
78
+
62
79
  it 'renders an unordered list with an embedded dl containing the metadata of the related item' do
63
80
  within(html.first('dd')) do |dd|
64
81
  expect(dd).to have_css('ul.mods_display_nested_related_items')
@@ -280,6 +280,17 @@ module ImprintFixtures
280
280
  MODS
281
281
  end
282
282
 
283
+ def iso8601_encoded_dates
284
+ <<-MODS
285
+ <mods>
286
+ <originInfo>
287
+ <dateCreated encoding="iso8601">20131114161429</dateCreated>
288
+ <dateModified encoding="iso8601">Jul. 22, 2013</dateModified>
289
+ </originInfo>
290
+ </mods>
291
+ MODS
292
+ end
293
+
283
294
  def bad_dates
284
295
  <<-MODS
285
296
  <mods>
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'byebug'
5
+
6
+ describe ModsDisplay::Helpers::RecordHelper, type: :helper do
7
+ let(:empty_field) { OpenStruct.new(label: 'test', values: ['']) }
8
+
9
+ describe 'display_content_field' do
10
+ let(:values) { ['guitar (1)', 'solo cowbell, trombone (2)'] }
11
+ let(:content) { OpenStruct.new(label: 'Instrumentation', values: values) }
12
+
13
+ it 'should return dt with label and dd with values' do
14
+ expect(helper.display_content_field(content)).to have_css('dt', text: 'Instrumentation')
15
+ expect(helper.display_content_field(content)).to have_css('dd', count: 2)
16
+ end
17
+ end
18
+
19
+ describe 'display_content_label' do
20
+ it 'should return correct dt' do
21
+ expect(helper.display_content_label('test')).to have_css('dt', text: 'test')
22
+ end
23
+ end
24
+
25
+ describe 'display_content_values' do
26
+ let(:values) { ['guitar (1)', 'solo cowbell, trombone (2)'] }
27
+
28
+ it 'should return dds of values' do
29
+ expect(helper.display_content_values(values)).to have_css('dd', count: 2)
30
+ expect(helper.display_content_values(values)).to have_css('dd', text: 'guitar (1)')
31
+ expect(helper.display_content_values(values)).to have_css('dd', text: 'solo cowbell, trombone (2)')
32
+ end
33
+ end
34
+
35
+ describe 'mods_display_label' do
36
+ it 'should return correct label' do
37
+ expect(helper.mods_display_label('test:')).not_to have_content ':'
38
+ expect(helper.mods_display_label('test:')).to have_css('dt', text: 'test')
39
+ end
40
+ end
41
+
42
+ describe 'mods_display_content' do
43
+ it 'should return correct content' do
44
+ expect(helper.mods_display_content('hello, there')).to have_css('dd', text: 'hello, there')
45
+ end
46
+ it 'should return multiple dd elements when a multi-element array is passed' do
47
+ expect(helper.mods_display_content(%w(hello there))).to have_css('dd', count: 2)
48
+ end
49
+ it 'should handle nil values correctly' do
50
+ expect(helper.mods_display_content(['something', nil])).to have_css('dd', count: 1)
51
+ end
52
+ end
53
+
54
+ describe 'mods_record_field' do
55
+ let(:mods_field) { OpenStruct.new(label: 'test', values: ['hello, there']) }
56
+ let(:url_field) { OpenStruct.new(label: 'test', values: ['https://library.stanford.edu']) }
57
+ let(:multi_values) { double(label: 'test', values: %w(123 321)) }
58
+
59
+ it 'should return correct content' do
60
+ expect(helper.mods_record_field(mods_field)).to have_css('dt', text: 'test')
61
+ expect(helper.mods_record_field(mods_field)).to have_css('dd', text: 'hello, there')
62
+ end
63
+ it 'should link fields with URLs' do
64
+ expect(mods_record_field(url_field)).to have_css("a[href='https://library.stanford.edu']", text: 'https://library.stanford.edu')
65
+ end
66
+ it 'should not print empty labels' do
67
+ expect(helper.mods_record_field(empty_field)).not_to be_present
68
+ end
69
+ it 'should join values with a <dd> by default' do
70
+ expect(helper.mods_record_field(multi_values)).to have_css('dd', count: 2)
71
+ end
72
+ it 'should join values with a supplied delimiter' do
73
+ expect(helper.mods_record_field(multi_values, 'DELIM')).to have_css('dd', count: 1)
74
+ expect(helper.mods_record_field(multi_values, 'DELIM')).to have_css('dd', text: '123DELIM321')
75
+ end
76
+ end
77
+
78
+ describe 'names' do
79
+ let(:name_field) do
80
+ OpenStruct.new(
81
+ label: 'Contributor',
82
+ values: [
83
+ OpenStruct.new(name: 'Winefrey, Oprah', roles: %w(Host Producer)),
84
+ OpenStruct.new(name: 'Kittenz, Emergency')
85
+ ]
86
+ )
87
+ end
88
+
89
+ describe '#mods_name_field' do
90
+ it 'should join the label and values' do
91
+ name = mods_name_field(name_field) do |name|
92
+ link_to(name, searches_path(q: "\"#{name}\"", search_field: 'search_author'))
93
+ end
94
+ expect(name).to match /<dt>Contributor<\/dt>/
95
+ expect(name).to match /<dd><a href.*<\/dd>/
96
+ end
97
+ it 'should not print empty labels' do
98
+ expect(mods_name_field(empty_field)).not_to be_present
99
+ end
100
+ end
101
+
102
+ describe '#mods_display_name' do
103
+ let(:name) do
104
+ mods_display_name(name_field.values) do |name|
105
+ link_to(name, searches_path(q: "\"#{name}\"", search_field: 'search_author'))
106
+ end
107
+ end
108
+
109
+ it 'should link to the name' do
110
+ expect(name).to match /<a href=.*%22Winefrey%2C\+Oprah%22.*>Winefrey, Oprah<\/a>/
111
+ expect(name).to match /<a href=.*%22Kittenz%2C\+Emergency%22.*>Kittenz, Emergency<\/a>/
112
+ end
113
+ it 'should link to an author search' do
114
+ expect(name).to match /<a href.*search_field=search_author.*>/
115
+ end
116
+ end
117
+
118
+ describe '#sanitize_mods_name_label' do
119
+ it 'removes a ":" at the end of label if present' do
120
+ expect(sanitize_mods_name_label('Test String:')).to eq 'Test String'
121
+ expect(sanitize_mods_name_label('Test String')).to eq 'Test String'
122
+ end
123
+ end
124
+ end
125
+
126
+ describe 'subjects' do
127
+ let(:subjects) { [OpenStruct.new(label: 'Subjects', values: [%w(Subject1a Subject1b), %w(Subject2a Subject2b Subject2c)])] }
128
+ let(:name_subjects) { [OpenStruct.new(label: 'Subjects', values: [OpenStruct.new(name: 'Person Name', roles: %w(Role1 Role2))])] }
129
+ let(:genres) { [OpenStruct.new(label: 'Genres', values: %w(Genre1 Genre2 Genre3))] }
130
+
131
+ describe '#mods_subject_field' do
132
+ let(:subject) do
133
+ mods_subject_field(subjects.first) do |subject_text|
134
+ link_to(
135
+ subject_text,
136
+ searches_path(
137
+ q: "\"#{[[], subject_text.strip].flatten.join(' ')}\"",
138
+ search_field: 'subject_terms'
139
+ )
140
+ )
141
+ end
142
+ end
143
+ it 'should join the subject fields in a dd' do
144
+ expect(subject).to match /<dd><a href=*.*\">Subject1a*.*Subject1b<\/a><\/dd><dd><a/
145
+ end
146
+ it "should join the individual subjects with a '>'" do
147
+ expect(subject).to match /Subject2b<\/a> &gt; <a href/
148
+ end
149
+ it 'should not print empty labels' do
150
+ expect(mods_subject_field(empty_field)).not_to be_present
151
+ end
152
+ end
153
+
154
+ describe '#mods_genre_field' do
155
+ it 'should join the genre fields with a dd' do
156
+ expect(mods_genre_field(genres.first) do |text|
157
+ link_to(text, searches_path(q: text))
158
+ end).to match /<dd><a href=*.*>Genre1*.*<\/a><\/dd><dd><a*.*Genre2<\/a><\/dd>/
159
+ end
160
+ it 'should not print empty labels' do
161
+ expect(mods_genre_field(empty_field)).not_to be_present
162
+ end
163
+ end
164
+
165
+ describe '#link_mods_subjects' do
166
+ let(:linked_subjects) do
167
+ link_mods_subjects(subjects.first.values.last) do |subject_text, buffer|
168
+ link_to(
169
+ subject_text,
170
+ searches_path(
171
+ q: "\"#{[buffer, subject_text.strip].flatten.join(' ')}\"",
172
+ search_field: 'subject_terms'
173
+ )
174
+ )
175
+ end
176
+ end
177
+
178
+ it 'should return all subjects' do
179
+ expect(linked_subjects.length).to eq 3
180
+ end
181
+ it 'should link to the subject hierarchically' do
182
+ expect(linked_subjects[0]).to match /^<a href=.*q=%22Subject2a%22.*>Subject2a<\/a>$/
183
+ expect(linked_subjects[1]).to match /^<a href=.*q=%22Subject2a\+Subject2b%22.*>Subject2b<\/a>$/
184
+ expect(linked_subjects[2]).to match /^<a href=.*q=%22Subject2a\+Subject2b\+Subject2c%22.*>Subject2c<\/a>$/
185
+ end
186
+ it 'should link to subject terms search field' do
187
+ linked_subjects.each do |subject|
188
+ expect(subject).to match /search_field=subject_terms/
189
+ end
190
+ end
191
+ end
192
+
193
+ describe '#link_mods_genres' do
194
+ let(:linked_genres) do
195
+ link_mods_genres(genres.first.values.last) do |text|
196
+ link_to(
197
+ text,
198
+ searches_path(
199
+ q: "\"#{[[], text.strip].flatten.join(' ')}\"",
200
+ search_field: 'subject_terms'
201
+ )
202
+ )
203
+ end
204
+ end
205
+
206
+ it 'should return correct link' do
207
+ expect(linked_genres).to match /<a href=*.*Genre3*.*<\/a>/
208
+ end
209
+ it 'should link to subject terms search field' do
210
+ expect(linked_genres).to match /search_field=subject_terms/
211
+ end
212
+ end
213
+
214
+ describe '#link_to_mods_subject' do
215
+ it 'should handle subjects that behave like names' do
216
+ name_subject = link_to_mods_subject(name_subjects.first.values.first, []) do |subject_text|
217
+ link_to(
218
+ subject_text,
219
+ searches_path(
220
+ q: "\"#{[[], subject_text.strip].flatten.join(' ')}\"",
221
+ search_field: 'subject_terms'
222
+ )
223
+ )
224
+ end
225
+ expect(name_subject).to match /<a href=.*%22Person\+Name%22.*>Person Name<\/a> \(Role1, Role2\)/
226
+ end
227
+ end
228
+ end
229
+
230
+ describe '#link_urls_and_email' do
231
+ let(:url) { 'This is a field that contains an https://library.stanford.edu URL' }
232
+ let(:email) { 'This is a field that contains an email@email.com address' }
233
+
234
+ it 'should link URLs' do
235
+ expect(link_urls_and_email(url)).to eq "This is a field that contains an <a href='https://library.stanford.edu'>https://library.stanford.edu</a> URL"
236
+ end
237
+ it 'should link email addresses' do
238
+ expect(link_urls_and_email(email)).to eq "This is a field that contains an <a href='mailto:email@email.com'>email@email.com</a> address"
239
+ end
240
+ end
241
+ end
@@ -5,6 +5,7 @@ def html_from_mods(xml, locale = nil)
5
5
  model = TestModel.new
6
6
  model.modsxml = xml
7
7
  I18n.locale = locale if locale
8
+ I18n.fallbacks[:fr] = [:fr, :en]
8
9
  TestController.new.render_mods_display(model)
9
10
  end
10
11
 
@@ -53,6 +54,14 @@ describe 'HTML Output' do
53
54
  expect(@multiple_titles.body).not_to include('<dd>Main Title</dd>')
54
55
  expect(@multiple_titles.body).to include('<dd>Alternate Title</dd>')
55
56
  end
57
+
58
+ it 'should allow access to the subTitle independently from the title (for use with #body or fields)' do
59
+ sub_title = @multiple_titles.subTitle
60
+ expect(sub_title.length).to eq 1
61
+ expect(sub_title.first).to be_a ModsDisplay::Values
62
+ expect(sub_title.first.label).to match(/^Alternative title/i)
63
+ expect(sub_title.first.values).to eq(['Alternate Title'])
64
+ end
56
65
  end
57
66
  describe 'individual fields' do
58
67
  it 'should return ModsDispaly::Values for the specefied field' do
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,11 @@
1
+ ENV['RAILS_ENV'] ||= 'test'
1
2
  require 'mods_display'
2
3
  require 'stanford-mods'
3
4
  require 'capybara'
5
+ require 'rails'
6
+ require 'fake_app'
7
+ require 'rspec/rails'
8
+ require 'mods_display/helpers/record_helper'
4
9
 
5
10
  Dir["#{File.expand_path('..', __FILE__)}/fixtures/*.rb"].each { |file| require file }
6
11
  # Load i18n test file.
@@ -24,6 +29,7 @@ RSpec.configure do |config|
24
29
  # the seed, which is printed after each run.
25
30
  # --seed 1234
26
31
  config.order = 'random'
32
+ config.include Rails.application.routes.url_helpers
27
33
  end
28
34
  class TestModel
29
35
  attr_accessor :modsxml
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mods_display
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jessie Keck
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-28 00:00:00.000000000 Z
11
+ date: 2021-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: stanford-mods
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rubocop
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +122,20 @@ dependencies:
108
122
  - - ">="
109
123
  - !ruby/object:Gem::Version
110
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rails
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '6.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '6.0'
111
139
  description: MODS Display is a gem to centralize the display logic of MODS medadata.
112
140
  email:
113
141
  - jessie.keck@gmail.com
@@ -115,10 +143,10 @@ executables: []
115
143
  extensions: []
116
144
  extra_rdoc_files: []
117
145
  files:
146
+ - ".github/workflows/ruby.yml"
118
147
  - ".gitignore"
119
148
  - ".rubocop.yml"
120
149
  - ".rubocop_todo.yml"
121
- - ".travis.yml"
122
150
  - Gemfile
123
151
  - LICENSE.txt
124
152
  - README.md
@@ -163,14 +191,17 @@ files:
163
191
  - lib/mods_display/fields/subject.rb
164
192
  - lib/mods_display/fields/title.rb
165
193
  - lib/mods_display/fields/values.rb
194
+ - lib/mods_display/helpers/record_helper.rb
166
195
  - lib/mods_display/html.rb
167
196
  - lib/mods_display/model_extension.rb
197
+ - lib/mods_display/railtie.rb
168
198
  - lib/mods_display/related_item_concerns.rb
169
199
  - lib/mods_display/relator_codes.rb
170
200
  - lib/mods_display/version.rb
171
201
  - mods_display.gemspec
172
202
  - spec/configuration/access_condition_spec.rb
173
203
  - spec/configuration/base_spec.rb
204
+ - spec/fake_app.rb
174
205
  - spec/fields/abstract_spec.rb
175
206
  - spec/fields/access_condition_spec.rb
176
207
  - spec/fields/audience_spec.rb
@@ -203,6 +234,7 @@ files:
203
234
  - spec/fixtures/related_item_fixtures.rb
204
235
  - spec/fixtures/subjects_fixtures.rb
205
236
  - spec/fixtures/title_fixtures.rb
237
+ - spec/helpers/record_helper_spec.rb
206
238
  - spec/integration/configuration_spec.rb
207
239
  - spec/integration/html_spec.rb
208
240
  - spec/integration/installation_spec.rb
@@ -226,8 +258,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
226
258
  - !ruby/object:Gem::Version
227
259
  version: '0'
228
260
  requirements: []
229
- rubyforge_project:
230
- rubygems_version: 2.6.11
261
+ rubygems_version: 3.1.4
231
262
  signing_key:
232
263
  specification_version: 4
233
264
  summary: The MODS Display gem allows implementers to configure a customized display
@@ -236,6 +267,7 @@ summary: The MODS Display gem allows implementers to configure a customized disp
236
267
  test_files:
237
268
  - spec/configuration/access_condition_spec.rb
238
269
  - spec/configuration/base_spec.rb
270
+ - spec/fake_app.rb
239
271
  - spec/fields/abstract_spec.rb
240
272
  - spec/fields/access_condition_spec.rb
241
273
  - spec/fields/audience_spec.rb
@@ -268,6 +300,7 @@ test_files:
268
300
  - spec/fixtures/related_item_fixtures.rb
269
301
  - spec/fixtures/subjects_fixtures.rb
270
302
  - spec/fixtures/title_fixtures.rb
303
+ - spec/helpers/record_helper_spec.rb
271
304
  - spec/integration/configuration_spec.rb
272
305
  - spec/integration/html_spec.rb
273
306
  - spec/integration/installation_spec.rb
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- notifications:
2
- email: false
3
- rvm:
4
- - 2.4.1
5
- - 2.3.4
6
- - 2.2.8