• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / pdc_discovery / 5af13607-7741-41ac-b110-c94c8a3ee0f0

pending completion
5af13607-7741-41ac-b110-c94c8a3ee0f0

Pull #441

circleci

hectorcorrea
Better error handling
Pull Request #441: Indexing to a new collection

99 of 99 new or added lines in 3 files covered. (100.0%)

1816 of 2220 relevant lines covered (81.8%)

95.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.0
/app/models/dataset_citation.rb
1
# frozen_string_literal: true
2

3
# Handles citations for datasets
4
# rubocop:disable Metrics/ParameterLists
5
# rubocop:disable Metrics/ClassLength
6
# rubocop:disable Style/NumericPredicate
7
# rubocop:disable Style/IfUnlessModifier
8
class DatasetCitation
1✔
9
  NEWLINE_INDENTED = "\r\n\t\t\t\t\t\t\t\t"
1✔
10

11
  # @param authors [<String>] Array of authors.
12
  # @param years [<String>] Array of years (expected 1 or 2 values).
13
  # @param title [String>] Title of the dataset
14
  # @param type [String] Type of the dataset (e.g. "Data set" or "Unpublished raw data")
15
  # @publisher [String] Publisher of the dataset
16
  # @doi [String] DOI URL
17
  def initialize(authors, years, title, type, publisher, doi)
1✔
18
    @authors = authors || []
8✔
19
    @years = years || []
8✔
20
    @title = title
8✔
21
    @type = type
8✔
22
    @publisher = publisher
8✔
23
    @doi = doi
8✔
24
  end
25

26
  def to_s(style)
1✔
27
    if style == "BibTeX"
×
28
      bibtex
×
29
    else
30
      apa
×
31
    end
32
  end
33

34
  # Returns a string with APA-ish citation for the dataset
35
  # Reference: https://libguides.usc.edu/APA7th/datasets#s-lg-box-22855503
36
  def apa
1✔
37
    apa_author = ''
5✔
38
    case @authors.count
5✔
39
    when 0
40
      # do nothing
41
    when 1
42
      apa_author += @authors.first
3✔
43
    when 2
44
      apa_author += @authors.join(' & ')
1✔
45
    else
46
      apa_author += @authors[0..-2].join(', ') + ', & ' + @authors[-1]
1✔
47
    end
48

49
    apa_year = ''
5✔
50
    case @years.count
5✔
51
    when 0
52
      # do nothing
53
    when 1
54
      apa_year += "(#{@years.first})"
4✔
55
    else
56
      apa_year += "(#{@years.join('-')})"
1✔
57
    end
58

59
    apa_title = DatasetCitation.custom_strip(@title)
5✔
60
    apa_title += " [#{@type}]" if @type.present?
5✔
61
    apa_title = append_dot(apa_title)
5✔
62

63
    apa_publisher = append_dot(@publisher)
5✔
64
    apa_doi = @doi
5✔
65

66
    tokens = [append_dot(apa_author), append_dot(apa_year), apa_title, apa_publisher, apa_doi].reject(&:blank?)
5✔
67
    tokens.join(' ')
5✔
68
  rescue => ex
69
    Rails.logger.error "Error generating APA citation for (#{@title}): #{ex.message}"
×
70
    nil
×
71
  end
72

73
  # Returns a string with BibTeX citation for the dataset.
74
  #
75
  # There is no set standard for datasets and therefore the format we produce
76
  # was put together from a variety of sources including mimicking what Zotero
77
  # does and looking at examples from Zenodo (e.g. https://zenodo.org/record/6062882/export/hx#.Yiejad9OnUL)
78
  #
79
  # Notice that we use the @electronic{...} identifier instead of @dataset{...} since
80
  # Zotero does not recognize the later.
81
  def bibtex
1✔
82
    tokens = []
1✔
83
    if @authors.count > 0
1✔
84
      tokens << bibtex_field_author('author', @authors)
1✔
85
    end
86

87
    if @title.present?
1✔
88
      tokens << bibtex_field('title', @title, '{{', '}}')
1✔
89
    end
90

91
    if @publisher.present?
1✔
92
      tokens << bibtex_field('publisher', @publisher, '{{', '}}')
1✔
93
    end
94

95
    if @years.count > 0
1✔
96
      tokens << bibtex_field('year', @years.first)
1✔
97
    end
98

99
    if @doi.present?
1✔
100
      tokens << bibtex_field('url', @doi, '{', '}')
1✔
101
    end
102

103
    text = "@electronic{#{bibtex_id},\r\n"
1✔
104
    text += tokens.map { |token| "\t#{token}" }.join(",\r\n") + "\r\n"
6✔
105
    text += "}"
1✔
106
    text
1✔
107
  rescue => ex
108
    Rails.logger.error "Error generating BibTex citation for (#{@title}): #{ex.message}"
×
109
    nil
×
110
  end
111

112
  # Return a string with the ContextObjects in Spans (COinS) information
113
  # https://en.wikipedia.org/wiki/COinS
114
  def coins
1✔
115
    tokens = []
1✔
116
    tokens << "url_ver=Z39.88-2004"
1✔
117
    tokens << "ctx_ver=Z39.88-2004"
1✔
118
    tokens << "rft.type=webpage"
1✔
119
    tokens << "rft_val_fmt=#{CGI.escape('info:ofi/fmt:kev:mtx:dc')}"
1✔
120

121
    if @title.present?
1✔
122
      tokens << "rft.title=#{CGI.escape(@title)}"
1✔
123
    end
124

125
    @authors.each do |author|
1✔
126
      tokens << "rft.au=#{CGI.escape(author)}"
1✔
127
    end
128

129
    if @years.count > 0
1✔
130
      tokens << "rft.date=#{CGI.escape(@years.first.to_s)}"
1✔
131
    end
132

133
    if @publisher.present?
1✔
134
      tokens << "rft.publisher=#{CGI.escape(@publisher)}"
1✔
135
    end
136

137
    if @doi.present?
1✔
138
      tokens << "rft.identifier=#{CGI.escape(@doi)}"
1✔
139
    end
140

141
    "<span class=\"Z3988\" title=\"#{tokens.join('&amp;')}\"></span>"
1✔
142
  rescue => ex
143
    Rails.logger.error "Error generating COinS citation for (#{@title}): #{ex.message}"
×
144
    nil
×
145
  end
146

147
  # Returns an ID value for a BibTex citation
148
  def bibtex_id
1✔
149
    author_id = 'unknown'
1✔
150
    if @authors.count > 0
1✔
151
      author_id = @authors.first.downcase.tr(' ', '_').gsub(/[^a-z0-9_]/, '')
1✔
152
    end
153
    year_id = @years.first&.to_s || 'unknown'
1✔
154
    "#{author_id}_#{year_id}"
1✔
155
  end
156

157
  # Breaks a string into lines of at most max_length.
158
  # Returns an array with the lines.
159
  def bibtex_lines(string, max_length = 40)
1✔
160
    string = string.to_s # handles non-string values gracefully
8✔
161
    lines = []
8✔
162
    until string.nil?
8✔
163
      # TODO: it would be nice it we break on spaces rather than in the middle of a word.
164
      lines << string[0..max_length - 1]
9✔
165
      string = string[max_length..-1]
9✔
166
    end
167
    lines
8✔
168
  end
169

170
  # Creates a line to represent a BibTex field.
171
  # Breaks long lines into smaller lines.
172
  # Examples:
173
  #
174
  #     field_name = {{ short value }}
175
  #     field_name = {{ very very very
176
  #                 very very very very
177
  #                 long value }}
178
  #
179
  def bibtex_field(name, value, open_tag = '', close_tag = '')
1✔
180
    value_trim = bibtex_lines(value).join(NEWLINE_INDENTED)
4✔
181
    name.ljust(12) + '= ' + open_tag + value_trim + close_tag
4✔
182
  end
183

184
  # Creates a line to represent multiple authors in a BibTex field
185
  # https://en.wikibooks.org/wiki/LaTeX/Bibliography_Management#Authors
186
  #
187
  # Example:
188
  #
189
  #     author = { author1 and
190
  #              author2 and
191
  #              author3 }
192
  #
193
  def bibtex_field_author(name, authors, open_tag = '{', close_tag = '}')
1✔
194
    value_trim = authors.join(" and #{NEWLINE_INDENTED}")
1✔
195
    name.ljust(12) + '= ' + open_tag + value_trim + close_tag
1✔
196
  end
197

198
  # Appends a dot to a string if it does not end with one.
199
  def append_dot(value)
1✔
200
    return nil if value.nil?
20✔
201
    return '' if value.empty?
20✔
202
    DatasetCitation.custom_strip(value) + '.'
20✔
203
  end
204

205
  # Strip a few specific characters that tend to mess up citations (e.g. trailing periods, commas, et cetera)
206
  def self.custom_strip(value)
1✔
207
    return nil if value.nil?
33✔
208
    return '' if value.empty?
32✔
209
    while true
31✔
210
      last_char = value[-1]
53✔
211
      break if last_char.nil? || !last_char.in?('. ,')
53✔
212
      value = value.chomp(last_char)
22✔
213
    end
214
    value
31✔
215
  end
216
end
217
# rubocop:enable Metrics/ParameterLists
218
# rubocop:enable Metrics/ClassLength
219
# rubocop:enable Style/NumericPredicate
220
# rubocop:enable Style/IfUnlessModifier
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc