mods_display 1.4.0 → 1.5.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
2
  SHA256:
3
- metadata.gz: cd385f9d4aea6c14524378831877c327c6fdfd2664356e148b457af9c26a5623
4
- data.tar.gz: b66857f71c766dc6edb03b8783d1d63486e94c63c8e9a71a0474f6b5a11d44cc
3
+ metadata.gz: 8f82b0048ccbf7b94b1c15326c364b4133adf8c241145e98809e6e258cc15164
4
+ data.tar.gz: '04349a0206e891f2ab84d23c7a182e5a5bd1b1e8227bb08deda07d23824b4511'
5
5
  SHA512:
6
- metadata.gz: 6b4760e8eee0c1eef22badaf5087b4095c4c88766c3b128bbaa1e841e2b1aa34294ced0be7199875b6aa57cdf1b3385dd5c11d624f4ce818afcc7fa4f1da25ab
7
- data.tar.gz: 93c93c4249b55816c9716b1c632d0b4c5b5a2874365e33139fbbf6cd9421f01d3aa9800d347c72ae7837bcf98ef763650e8151a3cab547cecb287a72ff7a9ace
6
+ metadata.gz: 5e8ae4be6ab26e6e30fa74f0b71cc3a03c19ca33906770c5cf5c9819ccf11be9c92281e5b6443b8cb8905b0df285c93470a963d2c11ad2a37c1390166b77ee54
7
+ data.tar.gz: 0e982271c57bdc0058dc5f005f2f0a9a84962e4ce01f1e0740bb3143e9332c2b8f4a398adc190520dd5509bdd8b85eb58bdeeaf8544910405759a703ff121198
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/README.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # ModsDisplay
2
2
 
3
- A gem for displaying MODS Metadata in a configurable way.
3
+ A gem for displaying MODS Metadata in a configurable way.
4
+
5
+ This gem is used to centralize the display logic of MODS metadata by creating html fragments such as
6
+
7
+ ```
8
+ <dt>Publisher</dt>
9
+ <dd>Diagon Alley Publications</dd>
10
+ ```
11
+
12
+ which can be styled in consuming apps such as SearchWorks or Exhibits.
4
13
 
5
14
  ## Demo
6
15
 
@@ -20,23 +29,6 @@ Or install it yourself as:
20
29
 
21
30
  $ gem install mods_display
22
31
 
23
- ## Release/Upgrade Notes
24
-
25
- #### v0.5.0
26
- There are three major changes in this version.
27
-
28
- 1. RelatedItem nodes with a type of `constituent` or `host` are now treated separately and will render the full MODS display of any nested metadata. If accessing the `ModsDisplay::Values` directly through their accessors (e.g. custom grouping), this new metadata is available under `.nested_related_items`.
29
- * _**Note:** You may want to style and add some javascript toggling behavior (see exhibits for an example)._
30
- 2. Name nodes are now grouped/labeled by their role. If you were iterating over the names and splitting them out by their labels, that will need to change.
31
- 3. Table of contents/Summaries are now split on `--` and rendered as an unordered list.
32
- * _**Note:** You may want to style this list._
33
-
34
- #### v0.3.0
35
-
36
- Labels now have internationalization support. We have added colons to the english labels due to certain languages' punctuation rules requiring different spacing between the label and colon.
37
-
38
- Given that fact, you will want to update any pre 0.3.0 code that searches for elements by label in a way that would fail with the presence of a colon.
39
-
40
32
  ## Contributing
41
33
 
42
34
  1. Fork it
@@ -0,0 +1,11 @@
1
+ <% values.each do |value| %>
2
+ <tr>
3
+ <% if label %>
4
+ <%= tag.th label, **label_html_attributes %>
5
+ <% end %>
6
+
7
+ <%= tag.td **value_html_attributes do %>
8
+ <%= value %>
9
+ <% end %>
10
+ </tr>
11
+ <% end %>
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class RowFieldComponent < ViewComponent::Base
5
+ with_collection_parameter :field
6
+
7
+ def initialize(field:, delimiter: nil, label_html_attributes: {}, value_html_attributes: {}, value_transformer: nil)
8
+ super
9
+
10
+ @field = field
11
+ @delimiter = delimiter
12
+ @value_transformer = value_transformer
13
+ @label_html_attributes = label_html_attributes
14
+ @value_html_attributes = value_html_attributes
15
+ end
16
+
17
+ attr_reader :field, :delimiter, :label_html_attributes, :value_html_attributes
18
+
19
+ def render?
20
+ field.values.any?(&:present?)
21
+ end
22
+
23
+ def format_value(value)
24
+ if @value_transformer
25
+ @value_transformer.call(value)
26
+ else
27
+ helpers.format_mods_html(value, field: field)
28
+ end
29
+ end
30
+
31
+ def values
32
+ if delimiter
33
+ [safe_join(field.values.select(&:present?).map { |value| format_value(value) }, delimiter)]
34
+ else
35
+ field.values.select(&:present?).map { |value| format_value(value) }
36
+ end
37
+ end
38
+
39
+ def label
40
+ field.label&.sub(/:$/, '')
41
+ end
42
+ end
43
+ end
@@ -17,6 +17,14 @@ module ModsDisplay
17
17
  render component.new(field: field, delimiter: delimiter, value_transformer: block)
18
18
  end
19
19
 
20
+ def mods_record_definition_field(field, delimiter: nil, label_html_attributes: {}, &block)
21
+ render ModsDisplay::FieldComponent.new(field: field, delimiter: delimiter, label_html_attributes: label_html_attributes, value_transformer: block)
22
+ end
23
+
24
+ def mods_record_row_field(field, delimiter: ', ', &block)
25
+ render ModsDisplay::RowFieldComponent.new(field: field, delimiter: delimiter, value_transformer: block)
26
+ end
27
+
20
28
  # this returns a role's label and the display names for ModsDisplay:Name:Person
21
29
  def mods_name_field(field)
22
30
  mods_record_field(field) do |name|
@@ -41,12 +49,24 @@ module ModsDisplay
41
49
  end
42
50
  end
43
51
 
52
+ def mods_subject_row_field(field, delimiter: nil, &block)
53
+ mods_record_row_field(field, delimiter: delimiter) do |subject_line|
54
+ safe_join(link_mods_subjects(subject_line, &block), ' > ')
55
+ end
56
+ end
57
+
44
58
  def mods_genre_field(field, &block)
45
59
  mods_record_field(field) do |genre_line|
46
60
  link_to_mods_subject(genre_line, &block)
47
61
  end
48
62
  end
49
63
 
64
+ def mods_genre_row_field(field, delimiter: nil, &block)
65
+ mods_record_row_field(field, delimiter: delimiter) do |genre_line|
66
+ link_to_mods_subject(genre_line, &block)
67
+ end
68
+ end
69
+
50
70
  # @private
51
71
  def link_mods_genres(genre, &block)
52
72
  link_buffer = []
@@ -17,11 +17,13 @@ en:
17
17
  creation_production_credits: "Creation/Production credits:"
18
18
  date_captured: "Date captured:"
19
19
  date_created: "Date created:"
20
+ date_issued: "Publication date:"
20
21
  date_modified: "Date modified:"
21
22
  date_sequential_designation: "Date/Sequential designation:"
22
23
  date_valid: "Date valid:"
23
24
  digital_origin: "Digital origin:"
24
25
  doi: "DOI:"
26
+ edition: "Edition:"
25
27
  extent: "Extent:"
26
28
  form: "Form:"
27
29
  frequency: "Frequency:"
@@ -18,7 +18,7 @@ module ModsDisplay
18
18
  [projection, coordinates].compact.join(' ')
19
19
  end
20
20
  return_fields << ModsDisplay::Values.new(
21
- label: (displayLabel(field) || label || I18n.t('mods_display.map_data')),
21
+ label: displayLabel(field) || label || I18n.t('mods_display.map_data'),
22
22
  values: [[scale, post_scale].compact.join(' ; ')]
23
23
  )
24
24
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class CopyrightDate < Field
5
+ def fields
6
+ date_fields(:copyrightDate)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class DateCaptured < Field
5
+ def fields
6
+ date_fields(:dateCaptured)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class DateCreated < Field
5
+ def fields
6
+ date_fields(:dateCreated)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class DateIssued < Field
5
+ def fields
6
+ date_fields(:dateIssued)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class DateModified < Field
5
+ def fields
6
+ date_fields(:dateModified)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class DateValid < Field
5
+ def fields
6
+ date_fields(:dateValid)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class Edition < Field
5
+ def fields
6
+ return_fields = @values.map do |value|
7
+ edition_value = Stanford::Mods::Imprint.new(value).edition_vals_str
8
+ next unless edition_value.present?
9
+
10
+ # remove trailing spaces (thanks MARC for catalog card formatting!)
11
+ edition_value.gsub!(%r{ */$}, '')
12
+
13
+ ModsDisplay::Values.new(
14
+ label: I18n.t('mods_display.edition'),
15
+ values: [edition_value],
16
+ field: self
17
+ )
18
+ end.compact
19
+ collapse_fields(return_fields)
20
+ end
21
+ end
22
+ end
@@ -57,5 +57,46 @@ module ModsDisplay
57
57
  def element_text(element)
58
58
  element.xpath('.//text()').to_html.strip
59
59
  end
60
+
61
+ # used for originInfo date fields, e.g. DateCreated, DateIssued ...
62
+ def date_fields(date_symbol)
63
+ return_fields = @values.map do |value|
64
+ date_values = Stanford::Mods::Imprint.new(value).dates([date_symbol])
65
+ next unless date_values.present?
66
+
67
+ ModsDisplay::Values.new(
68
+ label: I18n.t("mods_display.#{date_symbol.to_s.underscore}"),
69
+ values: select_best_date(date_values),
70
+ field: self
71
+ )
72
+ end.compact
73
+ collapse_fields(return_fields)
74
+ end
75
+
76
+ # used for originInfo dates, e.g. for Imprint, DateCreated, DateIssued, etc.
77
+ def select_best_date(dates)
78
+ # ensure dates are unique with respect to their base values
79
+ dates = dates.group_by(&:base_value).map do |_value, group|
80
+ group.first if group.one?
81
+
82
+ # if one of the duplicates wasn't encoded, use that one. see:
83
+ # https://consul.stanford.edu/display/chimera/MODS+display+rules#MODSdisplayrules-3b.%3CoriginInfo%3E
84
+ if group.reject(&:encoding).any?
85
+ group.reject(&:encoding).first
86
+
87
+ # otherwise just randomly pick the last in the group
88
+ else
89
+ group.last
90
+ end
91
+ end
92
+
93
+ # if any single dates are already part of a range, discard them
94
+ range_base_values = dates.select { |date| date.is_a?(Stanford::Mods::Imprint::DateRange) }
95
+ .map(&:base_values).flatten
96
+ dates = dates.reject { |date| range_base_values.include?(date.base_value) }
97
+
98
+ # output formatted dates with qualifiers, CE/BCE, etc.
99
+ dates.map(&:qualified_value)
100
+ end
60
101
  end
61
102
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class Frequency < Field
5
+ def fields
6
+ return_fields = @values.map do |origin_info_element|
7
+ frequency_value = origin_info_element.frequency&.text&.strip
8
+ next unless frequency_value.present?
9
+
10
+ ModsDisplay::Values.new(
11
+ label: I18n.t('mods_display.frequency'),
12
+ values: [frequency_value],
13
+ field: self
14
+ )
15
+ end.compact
16
+ collapse_fields(return_fields)
17
+ end
18
+ end
19
+ end
@@ -48,7 +48,7 @@ module ModsDisplay
48
48
 
49
49
  ModsDisplay::Values.new(
50
50
  label: displayLabel(element) || pub_info_labels[date_field],
51
- values: select_the_best_date(date_values)
51
+ values: select_best_date(date_values)
52
52
  )
53
53
  end.compact
54
54
  end
@@ -117,6 +117,8 @@ module ModsDisplay
117
117
  end.map(&:text).join(' ').strip
118
118
  end
119
119
 
120
+ # not an exact duplicate of the method in ModsDisplay::Place, particularly trailing punctuation code
121
+ # as ModsDisplay::Place is not intended to combine with publisher in an imprint string
120
122
  def place_element(value)
121
123
  return if value.place.text.strip.empty?
122
124
 
@@ -142,7 +144,7 @@ module ModsDisplay
142
144
  date_values = imprint.dates([date_field])
143
145
  next if date_values.empty?
144
146
 
145
- select_the_best_date(date_values)
147
+ select_best_date(date_values)
146
148
  end.flatten.compact.reject do |date|
147
149
  date.strip.empty?
148
150
  end.map(&:strip)
@@ -162,30 +164,5 @@ module ModsDisplay
162
164
  issuance: I18n.t('mods_display.issuance'),
163
165
  frequency: I18n.t('mods_display.frequency') }
164
166
  end
165
-
166
- def select_the_best_date(dates)
167
- # ensure dates are unique with respect to their base values
168
- dates = dates.group_by(&:base_value).map do |_value, group|
169
- group.first if group.one?
170
-
171
- # if one of the duplicates wasn't encoded, use that one. see:
172
- # https://consul.stanford.edu/display/chimera/MODS+display+rules#MODSdisplayrules-3b.%3CoriginInfo%3E
173
- if group.reject(&:encoding).any?
174
- group.reject(&:encoding).first
175
-
176
- # otherwise just randomly pick the last in the group
177
- else
178
- group.last
179
- end
180
- end
181
-
182
- # if any single dates are already part of a range, discard them
183
- range_base_values = dates.select { |date| date.is_a?(Stanford::Mods::Imprint::DateRange) }
184
- .map(&:base_values).flatten
185
- dates = dates.reject { |date| range_base_values.include?(date.base_value) }
186
-
187
- # output formatted dates with qualifiers, CE/BCE, etc.
188
- dates.map(&:qualified_value)
189
- end
190
167
  end
191
168
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class Issuance < Field
5
+ def fields
6
+ return_fields = @values.map do |origin_info_element|
7
+ issuance_value = origin_info_element.issuance&.text&.strip
8
+ next unless issuance_value.present?
9
+
10
+ ModsDisplay::Values.new(
11
+ label: displayLabel(origin_info_element) || I18n.t('mods_display.issuance'),
12
+ values: [issuance_value],
13
+ field: self
14
+ )
15
+ end.compact
16
+ collapse_fields(return_fields)
17
+ end
18
+ end
19
+ end
@@ -8,59 +8,66 @@ module ModsDisplay
8
8
  class NestedRelatedItem < Field
9
9
  include ModsDisplay::RelatedItemConcerns
10
10
 
11
+ def initialize(values, value_renderer: ValueRenderer)
12
+ super(values)
13
+ @value_renderer = value_renderer
14
+ end
15
+
11
16
  def fields
12
17
  @fields ||= begin
13
18
  return_fields = RelatedItemValue.for_values(@values).map do |value|
14
19
  next if value.collection?
15
20
  next unless render_nested_related_item?(value)
16
21
 
17
- related_item_mods_object(value)
22
+ related_item_text = @value_renderer.new(value).render
23
+
24
+ ModsDisplay::Values.new(
25
+ label: related_item_label(value),
26
+ values: [related_item_text]
27
+ )
18
28
  end.compact
19
29
  collapse_fields(return_fields)
20
30
  end
21
31
  end
22
32
 
23
- def to_html(view_context = ApplicationController.renderer)
24
- helpers = view_context.respond_to?(:simple_format) ? view_context : ApplicationController.new.view_context
33
+ class ValueRenderer
34
+ def initialize(value)
35
+ @value = value
36
+ end
25
37
 
26
- component = ModsDisplay::ListFieldComponent.with_collection(
27
- fields,
28
- value_transformer: ->(value) { helpers.format_mods_html(value.to_s) },
29
- list_html_attributes: { class: 'mods_display_nested_related_items' },
30
- list_item_html_attributes: { class: 'mods_display_nested_related_item open' }
31
- )
38
+ def render
39
+ [Array.wrap(mods_display_html.title).first, body_presence(mods_display_html.body)].compact.join
40
+ end
32
41
 
33
- view_context.render component, layout: false
34
- end
42
+ protected
35
43
 
36
- private
44
+ attr_reader :value
37
45
 
38
- def related_item_mods_object(value)
39
- mods = ::Stanford::Mods::Record.new.tap do |r|
40
- # dup'ing the value adds the appropriate namespaces, but...
41
- munged_node = value.dup.tap do |x|
42
- # ... the mods gem also expects the root of the document to have the root tag <mods>
43
- x.name = 'mods'
44
- end
45
-
46
- r.from_nk_node(munged_node)
46
+ def mods_display_html
47
+ @mods_display_html ||= ModsDisplay::HTML.new(mods)
47
48
  end
48
- related_item = ModsDisplay::HTML.new(mods)
49
49
 
50
- ModsDisplay::Values.new(
51
- label: related_item_label(value),
52
- values: [[Array.wrap(related_item.title).first, related_item_body(related_item)].compact.join]
53
- )
54
- end
50
+ def mods
51
+ @mods ||= ::Stanford::Mods::Record.new.tap do |r|
52
+ # dup'ing the value adds the appropriate namespaces, but...
53
+ munged_node = value.dup.tap do |x|
54
+ # ... the mods gem also expects the root of the document to have the root tag <mods>
55
+ x.name = 'mods'
56
+ end
55
57
 
56
- def related_item_body(related_item)
57
- body = related_item.body
58
+ r.from_nk_node(munged_node)
59
+ end
60
+ end
58
61
 
59
- return if body == '<dl></dl>'
62
+ def body_presence(body)
63
+ return if body == '<dl></dl>'
60
64
 
61
- body
65
+ body
66
+ end
62
67
  end
63
68
 
69
+ private
70
+
64
71
  def related_item_label(item)
65
72
  return displayLabel(item) if displayLabel(item)
66
73
  return I18n.t('mods_display.constituent') if item.constituent?
@@ -18,9 +18,9 @@ module ModsDisplay
18
18
 
19
19
  def note_fields
20
20
  @values.select do |value|
21
- (!value.attributes['type'].respond_to?(:value) ||
22
- (value.attributes['type'].respond_to?(:value) &&
23
- value.attributes['type'].value.downcase != 'contact'))
21
+ !value.attributes['type'].respond_to?(:value) ||
22
+ (value.attributes['type'].respond_to?(:value) &&
23
+ value.attributes['type'].value.downcase != 'contact')
24
24
  end
25
25
  end
26
26
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class Place < Field
5
+ include ModsDisplay::CountryCodes
6
+
7
+ def fields
8
+ return_fields = @values.map do |value|
9
+ place_value = place_element(value)
10
+ next unless place_value.present?
11
+
12
+ ModsDisplay::Values.new(
13
+ label: I18n.t('mods_display.place'),
14
+ values: [place_value],
15
+ field: self
16
+ )
17
+ end.compact
18
+ collapse_fields(return_fields)
19
+ end
20
+
21
+ private
22
+
23
+ # not an exact duplicate of the method in Imprint, particularly trailing punctuation code
24
+ def place_element(value)
25
+ return if value.place.text.strip.empty?
26
+
27
+ places = ModsDisplay::Imprint.new(value).place_terms(value).filter_map { |p| p.text unless p.text.strip.empty? }
28
+ compact_and_remove_trailing_delimiter(places, ':').join
29
+ end
30
+
31
+ def compact_and_remove_trailing_delimiter(values, delimiter = ':')
32
+ values.flatten.filter_map { |v| v.gsub(/ *#{delimiter}$/, '') if v.present? }.compact
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class Publisher < Field
5
+ def fields
6
+ return_fields = @values.map do |value|
7
+ publisher_value = Stanford::Mods::Imprint.new(value).publisher_vals_str
8
+ next unless publisher_value.present?
9
+
10
+ publisher_value.gsub!(/ *,$/, '')
11
+
12
+ ModsDisplay::Values.new(
13
+ label: I18n.t('mods_display.publisher'),
14
+ values: [publisher_value],
15
+ field: self
16
+ )
17
+ end.compact
18
+ collapse_fields(return_fields)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModsDisplay
4
+ class ReferenceTitle < Field
5
+ def fields
6
+ # Reference title is a composite field that includes parts from elsewhere in the record.
7
+ return [] unless @values.present?
8
+
9
+ return_fields = [ModsDisplay::Values.new(label: displayLabel(@values.first) || label, values: [title_value].compact)]
10
+ collapse_fields(return_fields)
11
+ end
12
+
13
+ private
14
+
15
+ def root
16
+ @root ||= @values.first.document.root
17
+ end
18
+
19
+ def displayLabel(element)
20
+ super(element) || I18n.t('mods_display.referenced_by')
21
+ end
22
+
23
+ def title_value
24
+ [@values,
25
+ root.xpath('mods:originInfo/mods:dateOther', mods: MODS_NS),
26
+ root.xpath('mods:part/mods:detail/mods:number', mods: MODS_NS),
27
+ root.xpath('mods:note', mods: MODS_NS)].flatten.compact.map!(&:text).map!(&:strip).join(' ').presence
28
+ end
29
+ end
30
+ end
@@ -4,12 +4,17 @@ module ModsDisplay
4
4
  class RelatedItem < Field
5
5
  include ModsDisplay::RelatedItemConcerns
6
6
 
7
+ def initialize(values, value_renderer: ValueRenderer)
8
+ super(values)
9
+ @value_renderer = value_renderer
10
+ end
11
+
7
12
  def fields
8
13
  return_fields = RelatedItemValue.for_values(@values).map do |value|
9
14
  next if value.collection?
10
15
  next if render_nested_related_item?(value)
11
16
 
12
- text = related_item_value(value)
17
+ text = @value_renderer.new(value).render
13
18
 
14
19
  next if text.nil? || text.empty?
15
20
 
@@ -18,41 +23,57 @@ module ModsDisplay
18
23
  collapse_fields(return_fields)
19
24
  end
20
25
 
21
- private
26
+ class ValueRenderer
27
+ def initialize(value)
28
+ @value = value
29
+ end
22
30
 
23
- def delimiter
24
- '<br />'.html_safe
25
- end
31
+ def render
32
+ if value.location?
33
+ element_text(value.location_nodeset)
34
+ elsif value.reference?
35
+ reference_title(value)
36
+ elsif value.titleInfo_nodeset.any?
37
+ title = element_text(value.titleInfo_nodeset)
38
+ location = nil
39
+ location = element_text(value.location_url_nodeset) if value.location_url_nodeset.length.positive?
40
+
41
+ return if title.empty?
26
42
 
27
- def related_item_value(item)
28
- if item.location?
29
- element_text(item.location_nodeset)
30
- elsif item.reference?
31
- reference_title(item)
32
- elsif item.titleInfo_nodeset.any?
33
- title = element_text(item.titleInfo_nodeset)
34
- location = nil
35
- location = element_text(item.location_url_nodeset) if item.location_url_nodeset.length.positive?
36
-
37
- return if title.empty?
38
-
39
- if location
40
- "<a href='#{location}'>#{title}</a>".html_safe
41
- else
42
- title
43
+ if location
44
+ "<a href='#{location}'>#{title}</a>".html_safe
45
+ else
46
+ title
47
+ end
48
+ elsif value.note_nodeset.any?
49
+ citation = value.note_nodeset.find { |note| note['type'] == 'preferred citation' }
50
+
51
+ element_text(citation) if citation
43
52
  end
44
- elsif item.note_nodeset.any?
45
- citation = item.note_nodeset.find { |note| note['type'] == 'preferred citation' }
53
+ end
46
54
 
47
- element_text(citation) if citation
55
+ protected
56
+
57
+ attr_reader :value
58
+
59
+ def element_text(element)
60
+ element.xpath('.//text()').to_html.strip
61
+ end
62
+
63
+ def reference_title(item)
64
+ [item.titleInfo_nodeset,
65
+ item.originInfo.dateOther,
66
+ item.part.detail.number,
67
+ item.note_nodeset].flatten.compact.map!(&:text).map!(&:strip).join(' ')
48
68
  end
49
69
  end
50
70
 
51
- def reference_title(item)
52
- [item.titleInfo_nodeset,
53
- item.originInfo.dateOther,
54
- item.part.detail.number,
55
- item.note_nodeset].flatten.compact.map!(&:text).map!(&:strip).join(' ')
71
+ private
72
+
73
+ attr_reader :value_renderer
74
+
75
+ def delimiter
76
+ '<br />'.html_safe
56
77
  end
57
78
 
58
79
  def related_item_label(item)
@@ -2,6 +2,11 @@
2
2
 
3
3
  module ModsDisplay
4
4
  class Title < Field
5
+ def initialize(values, link: nil)
6
+ @link = link
7
+ super(values)
8
+ end
9
+
5
10
  def fields
6
11
  return_values = sorted_values.map do |value|
7
12
  ModsDisplay::Values.new(
@@ -31,21 +36,7 @@ module ModsDisplay
31
36
  str = value.text.strip
32
37
  next if str.empty?
33
38
 
34
- delimiter = if title.empty? || title.end_with?(' ')
35
- nil
36
- elsif previous_element&.name == 'nonSort' && title.end_with?('-', '\'')
37
- nil
38
- elsif title.end_with?('.', ',', ':', ';')
39
- ' '
40
- elsif value.name == 'subTitle'
41
- ' : '
42
- elsif value.name == 'partName' && previous_element.name == 'partNumber'
43
- ', '
44
- elsif value.name == 'partNumber' || value.name == 'partName'
45
- '. '
46
- else
47
- ' '
48
- end
39
+ delimiter = title_delimiter(title, previous_element, value)
49
40
 
50
41
  title += delimiter if delimiter
51
42
  title += str
@@ -53,13 +44,38 @@ module ModsDisplay
53
44
  previous_element = value
54
45
  end
55
46
 
56
- if element['type'] == 'uniform' && element['nameTitleGroup'].present?
57
- [uniform_title_name_part(element), title].compact.join('. ')
47
+ full_title = if element['type'] == 'uniform' && element['nameTitleGroup'].present?
48
+ [uniform_title_name_part(element), title].compact.join('. ')
49
+ else
50
+ title
51
+ end
52
+ linked_title(full_title)
53
+ end
54
+
55
+ def title_delimiter(title, previous_element, value)
56
+ if title.empty? || title.end_with?(' ')
57
+ nil
58
+ elsif previous_element&.name == 'nonSort' && title.end_with?('-', '\'')
59
+ nil
60
+ elsif title.end_with?('.', ',', ':', ';')
61
+ ' '
62
+ elsif value.name == 'subTitle'
63
+ ' : '
64
+ elsif value.name == 'partName' && previous_element.name == 'partNumber'
65
+ ', '
66
+ elsif value.name == 'partNumber' || value.name == 'partName'
67
+ '. '
58
68
  else
59
- title
69
+ ' '
60
70
  end
61
71
  end
62
72
 
73
+ def linked_title(title)
74
+ return title unless @link
75
+
76
+ "<a href=\"#{@link}\">#{title}</a>"
77
+ end
78
+
63
79
  def uniform_title_name_part(element)
64
80
  names = element.xpath("//m:name[@nameTitleGroup=\"#{element['nameTitleGroup']}\"]", **Mods::Record::NS_HASH)
65
81
  names = element.xpath("//name[@nameTitleGroup=\"#{element['nameTitleGroup']}\"]") if names.empty?
@@ -5,13 +5,25 @@ module ModsDisplay
5
5
  MODS_DISPLAY_FIELD_MAPPING = {
6
6
  title: :title_info,
7
7
  subTitle: :title_info,
8
+ referenceTitle: :title_info,
8
9
  name: :plain_name,
9
10
  resourceType: :typeOfResource,
10
11
  genre: :genre,
11
12
  form: :physical_description,
12
13
  extent: :physical_description,
13
14
  geo: :extension,
15
+ copyrightDate: :origin_info,
16
+ dateCaptured: :origin_info,
17
+ dateCreated: :origin_info,
18
+ dateIssued: :origin_info,
19
+ dateModified: :origin_info,
20
+ dateValid: :origin_info,
21
+ edition: :origin_info,
22
+ frequency: :origin_info,
14
23
  imprint: :origin_info,
24
+ issuance: :origin_info,
25
+ place: :origin_info,
26
+ publisher: :origin_info,
15
27
  language: :language,
16
28
  description: :physical_description,
17
29
  cartographics: :subject,
@@ -29,11 +41,13 @@ module ModsDisplay
29
41
  accessCondition: :accessCondition
30
42
  }.freeze
31
43
 
32
- def initialize(xml)
33
- @stanford_mods = xml
34
- @xml = xml.mods_ng_xml
44
+ def initialize(mods)
45
+ @mods = mods
46
+ @xml = mods.mods_ng_xml
35
47
  end
36
48
 
49
+ attr_reader :xml
50
+
37
51
  def title
38
52
  title_fields = mods_field(:title).fields
39
53
  title_fields.empty? ? '' : title_fields.first.values
@@ -46,8 +60,8 @@ module ModsDisplay
46
60
  # Need to figure out how to get the 1st title out of the list.
47
61
  # Maybe have a separate class that will omit the first title natively
48
62
  # and replace the first key in the the fields list with that.
49
- def body(view_context = ApplicationController.renderer)
50
- view_context.render ModsDisplay::RecordComponent.new(record: self), layout: false
63
+ def body(view_context = ApplicationController.renderer, html_attributes: {}, component: ModsDisplay::RecordComponent)
64
+ view_context.render component.new(record: self, html_attributes: html_attributes), layout: false
51
65
  end
52
66
 
53
67
  # @deprecated
@@ -56,22 +70,22 @@ module ModsDisplay
56
70
  view_context.render ModsDisplay::RecordComponent.new(record: self, fields: fields), layout: false
57
71
  end
58
72
 
59
- MODS_DISPLAY_FIELD_MAPPING.each do |key, _value|
73
+ MODS_DISPLAY_FIELD_MAPPING.each_key do |key|
60
74
  next if key == :title
61
75
 
62
- define_method(key) do |raw: false|
63
- field = mods_field(key)
76
+ define_method(key) do |raw: false, **field_args|
77
+ field = mods_field(key, field_args: field_args)
64
78
  next field if raw
65
79
 
66
80
  field.fields
67
81
  end
68
82
  end
69
83
 
70
- def mods_field(key)
84
+ def mods_field(key, field_args: {})
71
85
  raise ArgumentError unless MODS_DISPLAY_FIELD_MAPPING[key] && @xml.respond_to?(MODS_DISPLAY_FIELD_MAPPING[key])
72
86
 
73
87
  field = @xml.public_send(MODS_DISPLAY_FIELD_MAPPING[key])
74
- mods_field_class(key).new(field)
88
+ mods_field_class(key).new(field, **field_args)
75
89
  end
76
90
 
77
91
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ModsDisplay
4
- VERSION = '1.4.0'
4
+ VERSION = '1.5.0'
5
5
  end
data/lib/mods_display.rb CHANGED
@@ -13,20 +13,32 @@ require 'mods_display/fields/audience'
13
13
  require 'mods_display/fields/collection'
14
14
  require 'mods_display/fields/contact'
15
15
  require 'mods_display/fields/contents'
16
+ require 'mods_display/fields/copyright_date'
16
17
  require 'mods_display/fields/cartographics'
18
+ require 'mods_display/fields/date_created'
19
+ require 'mods_display/fields/date_captured'
20
+ require 'mods_display/fields/date_issued'
21
+ require 'mods_display/fields/date_modified'
22
+ require 'mods_display/fields/date_valid'
17
23
  require 'mods_display/fields/description'
24
+ require 'mods_display/fields/edition'
18
25
  require 'mods_display/fields/extent'
26
+ require 'mods_display/fields/frequency'
19
27
  require 'mods_display/fields/form'
20
28
  require 'mods_display/fields/genre'
21
29
  require 'mods_display/fields/geo'
22
30
  require 'mods_display/fields/identifier'
23
31
  require 'mods_display/fields/imprint'
32
+ require 'mods_display/fields/issuance'
24
33
  require 'mods_display/fields/language'
25
34
  require 'mods_display/fields/location'
26
35
  require 'mods_display/fields/name'
27
36
  require 'mods_display/fields/nested_related_item'
28
37
  require 'mods_display/fields/note'
38
+ require 'mods_display/fields/place'
39
+ require 'mods_display/fields/publisher'
29
40
  require 'mods_display/fields/related_item'
41
+ require 'mods_display/fields/reference_title'
30
42
  require 'mods_display/fields/resource_type'
31
43
  require 'mods_display/fields/subject'
32
44
  require 'mods_display/fields/title'
data/mods_display.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |gem|
21
21
  gem.executables = gem.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
22
22
  gem.require_paths = ["lib"]
23
23
 
24
- gem.add_dependency 'stanford-mods', '~> 3.3', '>=3.3.7' # require stanford-mods 3.3.7 for orcid support
24
+ gem.add_dependency 'stanford-mods', '~> 3.3', '>=3.3.9' # require stanford-mods 3.3.9 for edition field
25
25
  gem.add_dependency 'i18n'
26
26
  gem.add_dependency 'view_component'
27
27
  gem.add_dependency 'rails_autolink'
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: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jessie Keck
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-10 00:00:00.000000000 Z
11
+ date: 2023-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: stanford-mods
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '3.3'
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 3.3.7
22
+ version: 3.3.9
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '3.3'
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 3.3.7
32
+ version: 3.3.9
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: i18n
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +177,7 @@ executables: []
177
177
  extensions: []
178
178
  extra_rdoc_files: []
179
179
  files:
180
+ - ".rspec"
180
181
  - ".rubocop.yml"
181
182
  - ".rubocop_todo.yml"
182
183
  - Gemfile
@@ -189,6 +190,8 @@ files:
189
190
  - app/components/mods_display/list_field_component.rb
190
191
  - app/components/mods_display/record_component.html.erb
191
192
  - app/components/mods_display/record_component.rb
193
+ - app/components/mods_display/row_field_component.html.erb
194
+ - app/components/mods_display/row_field_component.rb
192
195
  - app/helpers/mods_display/record_helper.rb
193
196
  - app/models/mods_display/record.rb
194
197
  - config.ru
@@ -203,19 +206,31 @@ files:
203
206
  - lib/mods_display/fields/collection.rb
204
207
  - lib/mods_display/fields/contact.rb
205
208
  - lib/mods_display/fields/contents.rb
209
+ - lib/mods_display/fields/copyright_date.rb
210
+ - lib/mods_display/fields/date_captured.rb
211
+ - lib/mods_display/fields/date_created.rb
212
+ - lib/mods_display/fields/date_issued.rb
213
+ - lib/mods_display/fields/date_modified.rb
214
+ - lib/mods_display/fields/date_valid.rb
206
215
  - lib/mods_display/fields/description.rb
216
+ - lib/mods_display/fields/edition.rb
207
217
  - lib/mods_display/fields/extent.rb
208
218
  - lib/mods_display/fields/field.rb
209
219
  - lib/mods_display/fields/form.rb
220
+ - lib/mods_display/fields/frequency.rb
210
221
  - lib/mods_display/fields/genre.rb
211
222
  - lib/mods_display/fields/geo.rb
212
223
  - lib/mods_display/fields/identifier.rb
213
224
  - lib/mods_display/fields/imprint.rb
225
+ - lib/mods_display/fields/issuance.rb
214
226
  - lib/mods_display/fields/language.rb
215
227
  - lib/mods_display/fields/location.rb
216
228
  - lib/mods_display/fields/name.rb
217
229
  - lib/mods_display/fields/nested_related_item.rb
218
230
  - lib/mods_display/fields/note.rb
231
+ - lib/mods_display/fields/place.rb
232
+ - lib/mods_display/fields/publisher.rb
233
+ - lib/mods_display/fields/reference_title.rb
219
234
  - lib/mods_display/fields/related_item.rb
220
235
  - lib/mods_display/fields/resource_type.rb
221
236
  - lib/mods_display/fields/sub_title.rb