mods_display 1.0.0.alpha4 → 1.0.0.alpha5
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 +4 -4
- data/.rubocop.yml +42 -0
- data/.rubocop_todo.yml +72 -401
- data/Gemfile +4 -1
- data/Rakefile +8 -6
- data/app/components/mods_display/field_component.rb +2 -0
- data/app/components/mods_display/list_field_component.rb +2 -0
- data/app/components/mods_display/record_component.rb +2 -0
- data/app/helpers/mods_display/record_helper.rb +4 -7
- data/app/models/mods_display/record.rb +2 -1
- data/config.ru +2 -2
- data/lib/mods_display/country_codes.rb +2 -1
- data/lib/mods_display/fields/abstract.rb +2 -0
- data/lib/mods_display/fields/access_condition.rb +7 -4
- data/lib/mods_display/fields/audience.rb +2 -0
- data/lib/mods_display/fields/cartographics.rb +8 -1
- data/lib/mods_display/fields/collection.rb +6 -2
- data/lib/mods_display/fields/contact.rb +2 -0
- data/lib/mods_display/fields/contents.rb +2 -0
- data/lib/mods_display/fields/description.rb +3 -2
- data/lib/mods_display/fields/extent.rb +3 -0
- data/lib/mods_display/fields/field.rb +6 -9
- data/lib/mods_display/fields/form.rb +3 -0
- data/lib/mods_display/fields/genre.rb +2 -0
- data/lib/mods_display/fields/geo.rb +5 -2
- data/lib/mods_display/fields/identifier.rb +20 -17
- data/lib/mods_display/fields/imprint.rb +168 -217
- data/lib/mods_display/fields/language.rb +5 -1
- data/lib/mods_display/fields/location.rb +4 -1
- data/lib/mods_display/fields/name.rb +35 -19
- data/lib/mods_display/fields/nested_related_item.rb +12 -2
- data/lib/mods_display/fields/note.rb +9 -7
- data/lib/mods_display/fields/related_item.rb +12 -9
- data/lib/mods_display/fields/resource_type.rb +2 -0
- data/lib/mods_display/fields/sub_title.rb +2 -0
- data/lib/mods_display/fields/subject.rb +17 -50
- data/lib/mods_display/fields/title.rb +37 -26
- data/lib/mods_display/fields/values.rb +2 -0
- data/lib/mods_display/html.rb +4 -3
- data/lib/mods_display/related_item_concerns.rb +4 -2
- data/lib/mods_display/relator_codes.rb +2 -0
- data/lib/mods_display/version.rb +3 -1
- data/lib/mods_display.rb +5 -3
- data/mods_display.gemspec +1 -1
- metadata +12 -6
| @@ -1,44 +1,51 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module ModsDisplay
         | 
| 2 4 | 
             
              class Imprint < Field
         | 
| 3 5 | 
             
                include ModsDisplay::CountryCodes
         | 
| 6 | 
            +
             | 
| 4 7 | 
             
                def fields
         | 
| 5 | 
            -
                   | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
                       | 
| 25 | 
            -
                        other_pub_info(value).each do |pub_info|
         | 
| 26 | 
            -
                          return_fields << ModsDisplay::Values.new(
         | 
| 27 | 
            -
                            label: displayLabel(value) || pub_info_labels[pub_info.name.to_sym],
         | 
| 28 | 
            -
                            values: [pub_info.text.strip]
         | 
| 29 | 
            -
                          )
         | 
| 30 | 
            -
                        end
         | 
| 31 | 
            -
                      end
         | 
| 8 | 
            +
                  collapse_fields(origin_info_data.flatten)
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def origin_info_data
         | 
| 12 | 
            +
                  @values.map do |value|
         | 
| 13 | 
            +
                    return_fields = []
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    edition = edition_element(value)
         | 
| 16 | 
            +
                    place = place_element(value)
         | 
| 17 | 
            +
                    publisher = publisher_element(value)
         | 
| 18 | 
            +
                    parts = parts_element(value)
         | 
| 19 | 
            +
                    place_pub = compact_and_join_with_delimiter([place, publisher], ' : ')
         | 
| 20 | 
            +
                    edition_place = compact_and_join_with_delimiter([edition, place_pub], ' - ')
         | 
| 21 | 
            +
                    joined_place_parts = compact_and_join_with_delimiter([edition_place, parts], ', ')
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    unless joined_place_parts.empty?
         | 
| 24 | 
            +
                      return_fields << ModsDisplay::Values.new(
         | 
| 25 | 
            +
                        label: displayLabel(value) || I18n.t('mods_display.imprint'),
         | 
| 26 | 
            +
                        values: [joined_place_parts]
         | 
| 27 | 
            +
                      )
         | 
| 32 28 | 
             
                    end
         | 
| 29 | 
            +
                    return_fields.concat(date_values(value))
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    other_pub_info(value).each do |pub_info|
         | 
| 32 | 
            +
                      return_fields << ModsDisplay::Values.new(
         | 
| 33 | 
            +
                        label: displayLabel(value) || pub_info_labels[pub_info.name.to_sym],
         | 
| 34 | 
            +
                        values: [pub_info.text.strip]
         | 
| 35 | 
            +
                      )
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    return_fields.compact
         | 
| 33 39 | 
             
                  end
         | 
| 34 | 
            -
                  collapse_fields(return_fields)
         | 
| 35 40 | 
             
                end
         | 
| 36 41 |  | 
| 37 | 
            -
                def  | 
| 42 | 
            +
                def date_values(element)
         | 
| 38 43 | 
             
                  date_field_keys.map do |date_field|
         | 
| 39 44 | 
             
                    next unless element.respond_to?(date_field)
         | 
| 45 | 
            +
             | 
| 40 46 | 
             
                    elements = element.send(date_field)
         | 
| 41 47 | 
             
                    next if elements.empty?
         | 
| 48 | 
            +
             | 
| 42 49 | 
             
                    ModsDisplay::Values.new(
         | 
| 43 50 | 
             
                      label: displayLabel(element) || pub_info_labels[elements.first.name.to_sym],
         | 
| 44 51 | 
             
                      values: parse_dates(elements)
         | 
| @@ -46,167 +53,147 @@ module ModsDisplay | |
| 46 53 | 
             
                  end.compact
         | 
| 47 54 | 
             
                end
         | 
| 48 55 |  | 
| 49 | 
            -
                 | 
| 50 | 
            -
             | 
| 51 | 
            -
                     | 
| 52 | 
            -
                      join_date_ranges(
         | 
| 53 | 
            -
                        process_bc_ad_dates(
         | 
| 54 | 
            -
                          process_encoded_dates(ignore_bad_dates(date_field))
         | 
| 55 | 
            -
                        )
         | 
| 56 | 
            -
                      )
         | 
| 57 | 
            -
                    )
         | 
| 58 | 
            -
                  )
         | 
| 59 | 
            -
                end
         | 
| 56 | 
            +
                class DateValue
         | 
| 57 | 
            +
                    attr_reader :value
         | 
| 58 | 
            +
                    delegate :text, :date, :point, :qualifier, :encoding, to: :value
         | 
| 60 59 |  | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
                     | 
| 64 | 
            -
                  end
         | 
| 65 | 
            -
                end
         | 
| 60 | 
            +
                    def initialize(value)
         | 
| 61 | 
            +
                      @value = value
         | 
| 62 | 
            +
                    end
         | 
| 66 63 |  | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
                      process_w3cdtf_date(date_field)
         | 
| 71 | 
            -
                    elsif date_is_iso8601?(date_field)
         | 
| 72 | 
            -
                      process_iso8601_date(date_field)
         | 
| 73 | 
            -
                    else
         | 
| 74 | 
            -
                      date_field
         | 
| 64 | 
            +
                    # True if the element text isn't blank or the placeholder "9999".
         | 
| 65 | 
            +
                    def valid?
         | 
| 66 | 
            +
                      text.present? && !['9999', '0000-00-00', 'uuuu'].include?(text.strip)
         | 
| 75 67 | 
             
                    end
         | 
| 76 | 
            -
                  end
         | 
| 77 | 
            -
                end
         | 
| 78 68 |  | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
                     | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
                      date = date.clone # clone the date object so we don't append the same one
         | 
| 85 | 
            -
                      if normalize_date(date.text) == normalize_date(start_date.text)
         | 
| 86 | 
            -
                        date.content = [start_date.text, end_date.text].join('-')
         | 
| 87 | 
            -
                        date
         | 
| 88 | 
            -
                      elsif normalize_date(date.text) != normalize_date(end_date.text)
         | 
| 89 | 
            -
                        date
         | 
| 69 | 
            +
                    # Element text reduced to digits and hyphen. Captures date ranges and
         | 
| 70 | 
            +
                    # negative (B.C.) dates. Used for comparison/deduping.
         | 
| 71 | 
            +
                    def base_value
         | 
| 72 | 
            +
                      if text =~ /^\[?1\d{3}-\d{2}\??\]?$/
         | 
| 73 | 
            +
                        return text.sub(/(\d{2})(\d{2})-(\d{2})/, '\1\2-\1\3')
         | 
| 90 74 | 
             
                      end
         | 
| 91 | 
            -
                    end.compact
         | 
| 92 | 
            -
                  elsif dates_are_open_range?(date_fields)
         | 
| 93 | 
            -
                    start_date = date_fields.find { |d| d.attributes['point'] && d.attributes['point'].value == 'start' }
         | 
| 94 | 
            -
                    date_fields.map do |date|
         | 
| 95 | 
            -
                      date = date.clone # clone the date object so we don't append the same one
         | 
| 96 | 
            -
                      date.content = "#{start_date.text}-" if date.text == start_date.text
         | 
| 97 | 
            -
                      date
         | 
| 98 | 
            -
                    end
         | 
| 99 | 
            -
                  else
         | 
| 100 | 
            -
                    date_fields
         | 
| 101 | 
            -
                  end
         | 
| 102 | 
            -
                end
         | 
| 103 75 |  | 
| 104 | 
            -
             | 
| 105 | 
            -
                  return_fields = date_fields.map do |date|
         | 
| 106 | 
            -
                    date = date.clone
         | 
| 107 | 
            -
                    if date_is_approximate?(date)
         | 
| 108 | 
            -
                      date.content = "[ca. #{date.text}]"
         | 
| 109 | 
            -
                    elsif date_is_questionable?(date)
         | 
| 110 | 
            -
                      date.content = "[#{date.text}?]"
         | 
| 111 | 
            -
                    elsif date_is_inferred?(date)
         | 
| 112 | 
            -
                      date.content = "[#{date.text}]"
         | 
| 76 | 
            +
                      text.gsub(/(?<![\d])(\d{1,3})([xu-]{1,3})/i) { "#{$1}#{'0' * $2.length}"}.scan(/[\d-]/).join
         | 
| 113 77 | 
             
                    end
         | 
| 114 | 
            -
                    date
         | 
| 115 | 
            -
                  end
         | 
| 116 | 
            -
                  return_fields.map(&:text)
         | 
| 117 | 
            -
                end
         | 
| 118 78 |  | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
                    date_field.attributes['qualifier'].value == 'approximate'
         | 
| 123 | 
            -
                end
         | 
| 79 | 
            +
                    # Decoded version of the date, if it was encoded. Strips leading zeroes.
         | 
| 80 | 
            +
                    def decoded_value
         | 
| 81 | 
            +
                      return text.strip unless date
         | 
| 124 82 |  | 
| 125 | 
            -
             | 
| 126 | 
            -
             | 
| 127 | 
            -
             | 
| 128 | 
            -
                    date_field.attributes['qualifier'].value == 'questionable'
         | 
| 129 | 
            -
                end
         | 
| 83 | 
            +
                      unless encoding.present?
         | 
| 84 | 
            +
                        return text.strip unless text =~ /^-?\d+$/ || text =~ /^[\dXxu?-]{4}$/
         | 
| 85 | 
            +
                      end
         | 
| 130 86 |  | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
             | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 87 | 
            +
                      # Delegate to the appropriate decoding method, if any
         | 
| 88 | 
            +
                      case value.precision
         | 
| 89 | 
            +
                      when :day
         | 
| 90 | 
            +
                        date.strftime('%B %e, %Y')
         | 
| 91 | 
            +
                      when :month
         | 
| 92 | 
            +
                        date.strftime('%B %Y')
         | 
| 93 | 
            +
                      when :year
         | 
| 94 | 
            +
                        year = date.year
         | 
| 95 | 
            +
                        if year < 1
         | 
| 96 | 
            +
                          "#{year.abs + 1} B.C."
         | 
| 97 | 
            +
                        # Any dates before the year 1000 are explicitly marked A.D.
         | 
| 98 | 
            +
                        elsif year > 1 && year < 1000
         | 
| 99 | 
            +
                          "#{year} A.D."
         | 
| 100 | 
            +
                        else
         | 
| 101 | 
            +
                          year.to_s
         | 
| 102 | 
            +
                        end
         | 
| 103 | 
            +
                      when :century
         | 
| 104 | 
            +
                        return "#{(date.to_s[0..1].to_i + 1).ordinalize} century"
         | 
| 105 | 
            +
                      when :decade
         | 
| 106 | 
            +
                        return "#{date.year}s"
         | 
| 107 | 
            +
                      else
         | 
| 108 | 
            +
                        text.strip
         | 
| 109 | 
            +
                      end
         | 
| 110 | 
            +
                    end
         | 
| 136 111 |  | 
| 137 | 
            -
             | 
| 138 | 
            -
             | 
| 139 | 
            -
                     | 
| 140 | 
            -
                       | 
| 141 | 
            -
             | 
| 142 | 
            -
             | 
| 143 | 
            -
             | 
| 144 | 
            -
                       | 
| 145 | 
            -
                      field.attributes['point'].value == 'end'
         | 
| 146 | 
            -
                  end
         | 
| 147 | 
            -
                end
         | 
| 112 | 
            +
                    # Decoded date with "B.C." or "A.D." and qualifier markers. See (outdated):
         | 
| 113 | 
            +
                    # https://consul.stanford.edu/display/chimera/MODS+display+rules#MODSdisplayrules-3b.%3CoriginInfo%3E
         | 
| 114 | 
            +
                    def qualified_value
         | 
| 115 | 
            +
                      date = decoded_value
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                      return "[ca. #{date}]" if qualifier == 'approximate'
         | 
| 118 | 
            +
                      return "[#{date}?]" if qualifier == 'questionable'
         | 
| 119 | 
            +
                      return "[#{date}]" if qualifier == 'inferred'
         | 
| 148 120 |  | 
| 149 | 
            -
             | 
| 150 | 
            -
                  attributes = date_fields.map do |date|
         | 
| 151 | 
            -
                    if date.attributes['point'].respond_to?(:value)
         | 
| 152 | 
            -
                      date.attributes['point'].value
         | 
| 121 | 
            +
                      date
         | 
| 153 122 | 
             
                    end
         | 
| 154 123 | 
             
                  end
         | 
| 155 | 
            -
                  attributes.include?('start') &&
         | 
| 156 | 
            -
                    attributes.include?('end')
         | 
| 157 | 
            -
                end
         | 
| 158 124 |  | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 125 | 
            +
                  class DateRange
         | 
| 126 | 
            +
                    def initialize(start: nil, stop: nil)
         | 
| 127 | 
            +
                      @start = start
         | 
| 128 | 
            +
                      @stop = stop
         | 
| 129 | 
            +
                    end
         | 
| 162 130 |  | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 131 | 
            +
                    # Base value as hyphen-joined string. Used for comparison/deduping.
         | 
| 132 | 
            +
                    def base_value
         | 
| 133 | 
            +
                      "#{@start&.base_value}-#{@stop&.base_value}"
         | 
| 134 | 
            +
                    end
         | 
| 166 135 |  | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 170 | 
            -
                    if date_field.text.strip =~ /^\d{4}-\d{2}-\d{2}$/
         | 
| 171 | 
            -
                      Date.parse(date_field.text).strftime('%B %d, %Y')
         | 
| 172 | 
            -
                    elsif date_field.text.strip =~ /^\d{4}-\d{2}$/
         | 
| 173 | 
            -
                      Date.parse("#{date_field.text}-01").strftime('%B %Y')
         | 
| 174 | 
            -
                    else
         | 
| 175 | 
            -
                      date_field.content
         | 
| 136 | 
            +
                    # Base values as array. Used for comparison/deduping of individual dates.
         | 
| 137 | 
            +
                    def base_values
         | 
| 138 | 
            +
                      [@start&.base_value, @stop&.base_value].compact
         | 
| 176 139 | 
             
                    end
         | 
| 177 | 
            -
                  rescue
         | 
| 178 | 
            -
                    date_field.content
         | 
| 179 | 
            -
                  end
         | 
| 180 | 
            -
                  date_field
         | 
| 181 | 
            -
                end
         | 
| 182 140 |  | 
| 183 | 
            -
             | 
| 184 | 
            -
             | 
| 185 | 
            -
             | 
| 186 | 
            -
                     | 
| 187 | 
            -
             | 
| 188 | 
            -
                     | 
| 141 | 
            +
                    # The encoding value for the start of the range, or stop if not present.
         | 
| 142 | 
            +
                    def encoding
         | 
| 143 | 
            +
                      @start&.encoding || @stop&.encoding
         | 
| 144 | 
            +
                    end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                    # Decoded dates with "B.C." or "A.D." and qualifier markers applied to
         | 
| 147 | 
            +
                    # the entire range, or individually if dates differ.
         | 
| 148 | 
            +
                    def qualified_value
         | 
| 149 | 
            +
                      if @start&.qualifier == @stop&.qualifier
         | 
| 150 | 
            +
                        qualifier = @start&.qualifier || @stop&.qualifier
         | 
| 151 | 
            +
                        date = "#{@start&.decoded_value}-#{@stop&.decoded_value}"
         | 
| 152 | 
            +
                        return "[ca. #{date}]" if qualifier == 'approximate'
         | 
| 153 | 
            +
                        return "[#{date}?]" if qualifier == 'questionable'
         | 
| 154 | 
            +
                        return "[#{date}]" if qualifier == 'inferred'
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                        date
         | 
| 157 | 
            +
                      else
         | 
| 158 | 
            +
                        "#{@start&.qualified_value}-#{@stop&.qualified_value}"
         | 
| 159 | 
            +
                      end
         | 
| 160 | 
            +
                    end
         | 
| 189 161 | 
             
                  end
         | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 162 | 
            +
                def parse_dates(elements)
         | 
| 163 | 
            +
                  # convert to DateValue objects and keep only valid ones
         | 
| 164 | 
            +
                  dates = elements.map(&:as_object).flatten.map { |element| DateValue.new(element) }.select(&:valid?)
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                  # join any date ranges into DateRange objects
         | 
| 167 | 
            +
                  point, nonpoint = dates.partition(&:point)
         | 
| 168 | 
            +
                  if point.any?
         | 
| 169 | 
            +
                    range = DateRange.new(start: point.find { |date| date.point == 'start' },
         | 
| 170 | 
            +
                                          stop: point.find { |date| date.point == 'end' })
         | 
| 171 | 
            +
                    nonpoint.unshift(range)
         | 
| 172 | 
            +
                  end
         | 
| 173 | 
            +
                  dates = nonpoint
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                  # ensure dates are unique with respect to their base values
         | 
| 176 | 
            +
                  dates = dates.group_by(&:base_value).map do |_value, group|
         | 
| 177 | 
            +
                    group.first if group.one?
         | 
| 192 178 |  | 
| 193 | 
            -
             | 
| 194 | 
            -
             | 
| 195 | 
            -
             | 
| 196 | 
            -
             | 
| 197 | 
            -
             | 
| 198 | 
            -
                     | 
| 199 | 
            -
                      [date_fields.find { |d| !d.attributes['encoding'] }]
         | 
| 179 | 
            +
                    # if one of the duplicates wasn't encoded, use that one. see:
         | 
| 180 | 
            +
                    # https://consul.stanford.edu/display/chimera/MODS+display+rules#MODSdisplayrules-3b.%3CoriginInfo%3E
         | 
| 181 | 
            +
                    if group.reject(&:encoding).any?
         | 
| 182 | 
            +
                      group.reject(&:encoding).first
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                    # otherwise just randomly pick the last in the group
         | 
| 200 185 | 
             
                    else
         | 
| 201 | 
            -
                       | 
| 186 | 
            +
                      group.last
         | 
| 202 187 | 
             
                    end
         | 
| 203 | 
            -
                  else
         | 
| 204 | 
            -
                    date_fields
         | 
| 205 188 | 
             
                  end
         | 
| 206 | 
            -
                end
         | 
| 207 189 |  | 
| 208 | 
            -
             | 
| 209 | 
            -
                  date. | 
| 190 | 
            +
                  # if any single dates are already part of a range, discard them
         | 
| 191 | 
            +
                  range_base_values = dates.select { |date| date.is_a?(DateRange) }
         | 
| 192 | 
            +
                                           .map(&:base_values).flatten
         | 
| 193 | 
            +
                  dates = dates.reject { |date| range_base_values.include?(date.base_value) }
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                  # output formatted dates with qualifiers, A.D./B.C., etc.
         | 
| 196 | 
            +
                  dates.map(&:qualified_value)
         | 
| 210 197 | 
             
                end
         | 
| 211 198 |  | 
| 212 199 | 
             
                def other_pub_info(element)
         | 
| @@ -218,6 +205,7 @@ module ModsDisplay | |
| 218 205 | 
             
                def place_terms(element)
         | 
| 219 206 | 
             
                  return [] unless element.respond_to?(:place) &&
         | 
| 220 207 | 
             
                                   element.place.respond_to?(:placeTerm)
         | 
| 208 | 
            +
             | 
| 221 209 | 
             
                  if unencoded_place_terms?(element)
         | 
| 222 210 | 
             
                    element.place.placeTerm.select do |term|
         | 
| 223 211 | 
             
                      !term.attributes['type'].respond_to?(:value) ||
         | 
| @@ -230,6 +218,7 @@ module ModsDisplay | |
| 230 218 | 
             
                                  term.attributes['authority'].respond_to?(:value) &&
         | 
| 231 219 | 
             
                                  term.attributes['authority'].value == 'marccountry' &&
         | 
| 232 220 | 
             
                                  country_codes.include?(term.text.strip)
         | 
| 221 | 
            +
             | 
| 233 222 | 
             
                      term = term.clone
         | 
| 234 223 | 
             
                      term.content = country_codes[term.text.strip]
         | 
| 235 224 | 
             
                      term
         | 
| @@ -244,63 +233,23 @@ module ModsDisplay | |
| 244 233 | 
             
                  end
         | 
| 245 234 | 
             
                end
         | 
| 246 235 |  | 
| 247 | 
            -
                def imprint_display_form(element)
         | 
| 248 | 
            -
                  display_form = element.children.find do |child|
         | 
| 249 | 
            -
                    child.name == 'displayForm'
         | 
| 250 | 
            -
                  end
         | 
| 251 | 
            -
                  ModsDisplay::Values.new(
         | 
| 252 | 
            -
                    label: displayLabel(element) || I18n.t('mods_display.imprint'),
         | 
| 253 | 
            -
                    values: [display_form.text]
         | 
| 254 | 
            -
                  ) if display_form
         | 
| 255 | 
            -
                end
         | 
| 256 | 
            -
             | 
| 257 236 | 
             
                private
         | 
| 258 237 |  | 
| 259 238 | 
             
                def compact_and_join_with_delimiter(values, delimiter)
         | 
| 260 239 | 
             
                  compact_values = values.compact.reject { |v| v.strip.empty? }
         | 
| 261 240 | 
             
                  return compact_values.join(delimiter) if compact_values.length == 1 ||
         | 
| 262 241 | 
             
                                                           !ends_in_terminating_punctuation?(delimiter)
         | 
| 242 | 
            +
             | 
| 263 243 | 
             
                  compact_values.each_with_index.map do |value, i|
         | 
| 264 | 
            -
                    if (compact_values.length - 1) == i || # last item?
         | 
| 265 | 
            -
             | 
| 266 | 
            -
             | 
| 267 | 
            -
             | 
| 268 | 
            -
             | 
| 269 | 
            -
             | 
| 244 | 
            +
                    value << if (compact_values.length - 1) == i || # last item?
         | 
| 245 | 
            +
                                ends_in_terminating_punctuation?(value)
         | 
| 246 | 
            +
                               ' '
         | 
| 247 | 
            +
                             else
         | 
| 248 | 
            +
                               delimiter
         | 
| 249 | 
            +
                             end
         | 
| 270 250 | 
             
                  end.join.strip
         | 
| 271 251 | 
             
                end
         | 
| 272 252 |  | 
| 273 | 
            -
                def process_bc_ad_dates(date_fields)
         | 
| 274 | 
            -
                  date_fields.map do |date_field|
         | 
| 275 | 
            -
                    case
         | 
| 276 | 
            -
                    when date_is_bc_edtf?(date_field)
         | 
| 277 | 
            -
                      year = date_field.text.strip.gsub(/^-0*/, '').to_i + 1
         | 
| 278 | 
            -
                      date_field.content = "#{year} B.C."
         | 
| 279 | 
            -
                    when date_is_ad?(date_field)
         | 
| 280 | 
            -
                      date_field.content = "#{date_field.text.strip.gsub(/^0*/, '')} A.D."
         | 
| 281 | 
            -
                    end
         | 
| 282 | 
            -
                    date_field
         | 
| 283 | 
            -
                  end
         | 
| 284 | 
            -
                end
         | 
| 285 | 
            -
             | 
| 286 | 
            -
                def date_is_bc_edtf?(date_field)
         | 
| 287 | 
            -
                  date_field.text.strip.start_with?('-') && date_is_edtf?(date_field)
         | 
| 288 | 
            -
                end
         | 
| 289 | 
            -
             | 
| 290 | 
            -
                def date_is_ad?(date_field)
         | 
| 291 | 
            -
                  date_field.text.strip.gsub(/^0*/, '').length < 4
         | 
| 292 | 
            -
                end
         | 
| 293 | 
            -
             | 
| 294 | 
            -
                def date_is_edtf?(date_field)
         | 
| 295 | 
            -
                  field_is_encoded?(date_field, 'edtf')
         | 
| 296 | 
            -
                end
         | 
| 297 | 
            -
             | 
| 298 | 
            -
                def field_is_encoded?(field, encoding)
         | 
| 299 | 
            -
                  field.attributes['encoding'] &&
         | 
| 300 | 
            -
                    field.attributes['encoding'].respond_to?(:value) &&
         | 
| 301 | 
            -
                    field.attributes['encoding'].value.downcase == encoding
         | 
| 302 | 
            -
                end
         | 
| 303 | 
            -
             | 
| 304 253 | 
             
                def ends_in_terminating_punctuation?(value)
         | 
| 305 254 | 
             
                  value.strip.end_with?('.', ',', ':', ';')
         | 
| 306 255 | 
             
                end
         | 
| @@ -313,6 +262,7 @@ module ModsDisplay | |
| 313 262 |  | 
| 314 263 | 
             
                def place_element(value)
         | 
| 315 264 | 
             
                  return if value.place.text.strip.empty?
         | 
| 265 | 
            +
             | 
| 316 266 | 
             
                  places = place_terms(value).reject do |p|
         | 
| 317 267 | 
             
                    p.text.strip.empty?
         | 
| 318 268 | 
             
                  end.map(&:text)
         | 
| @@ -321,6 +271,7 @@ module ModsDisplay | |
| 321 271 |  | 
| 322 272 | 
             
                def publisher_element(value)
         | 
| 323 273 | 
             
                  return if value.publisher.text.strip.empty?
         | 
| 274 | 
            +
             | 
| 324 275 | 
             
                  publishers = value.publisher.reject do |p|
         | 
| 325 276 | 
             
                    p.text.strip.empty?
         | 
| 326 277 | 
             
                  end.map(&:text)
         | 
| @@ -328,8 +279,9 @@ module ModsDisplay | |
| 328 279 | 
             
                end
         | 
| 329 280 |  | 
| 330 281 | 
             
                def parts_element(value)
         | 
| 331 | 
            -
                  date_elements = %w | 
| 282 | 
            +
                  date_elements = %w[dateIssued dateOther].map do |date_field_name|
         | 
| 332 283 | 
             
                    next unless value.respond_to?(date_field_name.to_sym)
         | 
| 284 | 
            +
             | 
| 333 285 | 
             
                    parse_dates(value.send(date_field_name.to_sym))
         | 
| 334 286 | 
             
                  end.flatten.compact.reject do |date|
         | 
| 335 287 | 
             
                    date.strip.empty?
         | 
| @@ -338,11 +290,11 @@ module ModsDisplay | |
| 338 290 | 
             
                end
         | 
| 339 291 |  | 
| 340 292 | 
             
                def pub_info_parts
         | 
| 341 | 
            -
                  [ | 
| 293 | 
            +
                  %i[issuance frequency]
         | 
| 342 294 | 
             
                end
         | 
| 343 295 |  | 
| 344 296 | 
             
                def date_field_keys
         | 
| 345 | 
            -
                  [ | 
| 297 | 
            +
                  %i[dateCreated dateCaptured dateValid dateModified copyrightDate]
         | 
| 346 298 | 
             
                end
         | 
| 347 299 |  | 
| 348 300 | 
             
                def pub_info_labels
         | 
| @@ -352,8 +304,7 @@ module ModsDisplay | |
| 352 304 | 
             
                    dateModified: I18n.t('mods_display.date_modified'),
         | 
| 353 305 | 
             
                    copyrightDate: I18n.t('mods_display.copyright_date'),
         | 
| 354 306 | 
             
                    issuance: I18n.t('mods_display.issuance'),
         | 
| 355 | 
            -
                    frequency: I18n.t('mods_display.frequency')
         | 
| 356 | 
            -
                  }
         | 
| 307 | 
            +
                    frequency: I18n.t('mods_display.frequency') }
         | 
| 357 308 | 
             
                end
         | 
| 358 309 | 
             
              end
         | 
| 359 310 | 
             
            end
         | 
| @@ -1,13 +1,17 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module ModsDisplay
         | 
| 2 4 | 
             
              class Language < Field
         | 
| 3 5 | 
             
                def fields
         | 
| 4 6 | 
             
                  return_fields = @values.map do |value|
         | 
| 5 7 | 
             
                    next unless value.respond_to?(:languageTerm)
         | 
| 8 | 
            +
             | 
| 6 9 | 
             
                    value.languageTerm.map do |term|
         | 
| 7 10 | 
             
                      next unless term.attributes['type'].respond_to?(:value) && term.attributes['type'].value == 'code'
         | 
| 11 | 
            +
             | 
| 8 12 | 
             
                      ModsDisplay::Values.new(
         | 
| 9 13 | 
             
                        label: displayLabel(value) || displayLabel(term) || I18n.t('mods_display.language'),
         | 
| 10 | 
            -
                        values: [ | 
| 14 | 
            +
                        values: [language_codes[term.text]]
         | 
| 11 15 | 
             
                      )
         | 
| 12 16 | 
             
                    end.flatten.compact
         | 
| 13 17 | 
             
                  end.flatten.compact
         | 
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module ModsDisplay
         | 
| 2 4 | 
             
              class Location < Field
         | 
| 3 5 | 
             
                def fields
         | 
| @@ -5,6 +7,7 @@ module ModsDisplay | |
| 5 7 | 
             
                  @values.each do |location|
         | 
| 6 8 | 
             
                    location.children.each do |child|
         | 
| 7 9 | 
             
                      next unless location_field_keys.include?(child.name.to_sym)
         | 
| 10 | 
            +
             | 
| 8 11 | 
             
                      if child.name.to_sym == :url
         | 
| 9 12 | 
             
                        loc_label = displayLabel(location) || I18n.t('mods_display.location')
         | 
| 10 13 | 
             
                        value = "<a href='#{child.text}'>#{(displayLabel(child) || child.text).gsub(/:$/, '')}</a>"
         | 
| @@ -24,7 +27,7 @@ module ModsDisplay | |
| 24 27 | 
             
                private
         | 
| 25 28 |  | 
| 26 29 | 
             
                def location_field_keys
         | 
| 27 | 
            -
                  [ | 
| 30 | 
            +
                  %i[physicalLocation url shelfLocator holdingSimple holdingExternal]
         | 
| 28 31 | 
             
                end
         | 
| 29 32 |  | 
| 30 33 | 
             
                def location_label(element)
         | 
| @@ -1,9 +1,11 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module ModsDisplay
         | 
| 2 4 | 
             
              class Name < Field
         | 
| 3 5 | 
             
                include ModsDisplay::RelatorCodes
         | 
| 4 6 | 
             
                def fields
         | 
| 5 7 | 
             
                  return_fields = @values.map do |value|
         | 
| 6 | 
            -
                    person = if value.displayForm.length | 
| 8 | 
            +
                    person = if value.displayForm.length.positive?
         | 
| 7 9 | 
             
                               ModsDisplay::Name::Person.new(name: value.displayForm.text)
         | 
| 8 10 | 
             
                             elsif !name_parts(value).empty?
         | 
| 9 11 | 
             
                               ModsDisplay::Name::Person.new(name: name_parts(value))
         | 
| @@ -40,11 +42,25 @@ module ModsDisplay | |
| 40 42 | 
             
                def role_labels(element)
         | 
| 41 43 | 
             
                  default_label = I18n.t('mods_display.associated_with')
         | 
| 42 44 | 
             
                  return [default_label] unless element.role.present? && element.role.roleTerm.present?
         | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            +
             | 
| 46 | 
            +
                  element.role.collect do |role|
         | 
| 47 | 
            +
                    codes, text = role.roleTerm.partition { |term| term['type'] == 'code' }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    # prefer mappable role term codes
         | 
| 50 | 
            +
                    label = codes.map { |term| relator_codes[term.text.downcase] }.first
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    # but fall back to given text
         | 
| 53 | 
            +
                    label ||= text.map { |term| format_role(term) }.first
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    # or just the default
         | 
| 56 | 
            +
                    label || default_label
         | 
| 45 57 | 
             
                  end.uniq
         | 
| 46 58 | 
             
                end
         | 
| 47 59 |  | 
| 60 | 
            +
                def format_role(element)
         | 
| 61 | 
            +
                  element.text.capitalize.sub(/[.,:;]+$/, '')
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 48 64 | 
             
                def role?(element)
         | 
| 49 65 | 
             
                  element.respond_to?(:role) && !element.role.empty?
         | 
| 50 66 | 
             
                end
         | 
| @@ -55,10 +71,9 @@ module ModsDisplay | |
| 55 71 | 
             
                end
         | 
| 56 72 |  | 
| 57 73 | 
             
                def name_parts(element)
         | 
| 58 | 
            -
                  output =  | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
                             qualified_name_parts(element, 'given')].flatten.compact.join(', ')
         | 
| 74 | 
            +
                  output = [unqualified_name_parts(element),
         | 
| 75 | 
            +
                            qualified_name_parts(element, 'family'),
         | 
| 76 | 
            +
                            qualified_name_parts(element, 'given')].flatten.compact.join(', ')
         | 
| 62 77 | 
             
                  terms = qualified_name_parts(element, 'termsOfAddress')
         | 
| 63 78 | 
             
                  unless terms.empty?
         | 
| 64 79 | 
             
                    term_delimiter = ', '
         | 
| @@ -66,9 +81,7 @@ module ModsDisplay | |
| 66 81 | 
             
                    output = [output, terms.join(', ')].flatten.compact.join(term_delimiter)
         | 
| 67 82 | 
             
                  end
         | 
| 68 83 | 
             
                  dates = qualified_name_parts(element, 'date')
         | 
| 69 | 
            -
                  unless dates.empty?
         | 
| 70 | 
            -
                    output = [output, qualified_name_parts(element, 'date')].flatten.compact.join(', ')
         | 
| 71 | 
            -
                  end
         | 
| 84 | 
            +
                  output = [output, qualified_name_parts(element, 'date')].flatten.compact.join(', ') unless dates.empty?
         | 
| 72 85 | 
             
                  output
         | 
| 73 86 | 
             
                end
         | 
| 74 87 |  | 
| @@ -90,7 +103,7 @@ module ModsDisplay | |
| 90 103 | 
             
                def name_part_begins_with_roman_numeral?(part)
         | 
| 91 104 | 
             
                  first_part = part.split(/\s|,/).first.strip
         | 
| 92 105 | 
             
                  first_part.chars.all? do |char|
         | 
| 93 | 
            -
                    %w | 
| 106 | 
            +
                    %w[I X C L V].include? char
         | 
| 94 107 | 
             
                  end
         | 
| 95 108 | 
             
                end
         | 
| 96 109 |  | 
| @@ -98,14 +111,16 @@ module ModsDisplay | |
| 98 111 | 
             
                  roles = element.role.map do |role|
         | 
| 99 112 | 
             
                    role.roleTerm.find do |term|
         | 
| 100 113 | 
             
                      term.attributes['type'].respond_to?(:value) &&
         | 
| 101 | 
            -
             | 
| 114 | 
            +
                        term.attributes['type'].value == 'text'
         | 
| 102 115 | 
             
                    end
         | 
| 103 116 | 
             
                  end.compact
         | 
| 104 | 
            -
                  roles | 
| 105 | 
            -
                    role. | 
| 106 | 
            -
                       | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 117 | 
            +
                  if roles.empty?
         | 
| 118 | 
            +
                    roles = element.role.map do |role|
         | 
| 119 | 
            +
                      role.roleTerm.find do |term|
         | 
| 120 | 
            +
                        !term.attributes['type'].respond_to?(:value)
         | 
| 121 | 
            +
                      end
         | 
| 122 | 
            +
                    end.compact
         | 
| 123 | 
            +
                  end
         | 
| 109 124 | 
             
                  roles.map { |t| t.text.strip }
         | 
| 110 125 | 
             
                end
         | 
| 111 126 |  | 
| @@ -130,7 +145,7 @@ module ModsDisplay | |
| 130 145 | 
             
                # Normalize label headings by filtering out some punctuation and ending in :
         | 
| 131 146 | 
             
                def normalize_labels(label_order, results)
         | 
| 132 147 | 
             
                  label_order.uniq.map do |k|
         | 
| 133 | 
            -
                    label = k.tr('.', '').tr(':', '').strip | 
| 148 | 
            +
                    label = "#{k.tr('.', '').tr(':', '').strip}:"
         | 
| 134 149 | 
             
                    if label != k
         | 
| 135 150 | 
             
                      results[label] = results[k]
         | 
| 136 151 | 
             
                      results.delete(k)
         | 
| @@ -152,8 +167,9 @@ module ModsDisplay | |
| 152 167 |  | 
| 153 168 | 
             
                class Person
         | 
| 154 169 | 
             
                  attr_accessor :name
         | 
| 170 | 
            +
             | 
| 155 171 | 
             
                  def initialize(data)
         | 
| 156 | 
            -
                    @name = | 
| 172 | 
            +
                    @name = data[:name]
         | 
| 157 173 | 
             
                  end
         | 
| 158 174 |  | 
| 159 175 | 
             
                  def to_s
         |