bolognese 0.7.2 → 0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +4 -1
  3. data/README.md +25 -16
  4. data/bolognese.gemspec +2 -1
  5. data/codemeta.json +39 -0
  6. data/lib/bolognese.rb +4 -0
  7. data/lib/bolognese/array.rb +11 -0
  8. data/lib/bolognese/author_utils.rb +35 -21
  9. data/lib/bolognese/bibtex.rb +4 -4
  10. data/lib/bolognese/codemeta.rb +8 -13
  11. data/lib/bolognese/crossref.rb +22 -20
  12. data/lib/bolognese/datacite.rb +61 -61
  13. data/lib/bolognese/datacite_json.rb +208 -0
  14. data/lib/bolognese/datacite_utils.rb +17 -48
  15. data/lib/bolognese/metadata.rb +83 -22
  16. data/lib/bolognese/schema_org.rb +42 -16
  17. data/lib/bolognese/utils.rb +79 -13
  18. data/lib/bolognese/version.rb +1 -1
  19. data/lib/bolognese/whitelist_scrubber.rb +45 -0
  20. data/spec/array_spec.rb +20 -0
  21. data/spec/author_utils_spec.rb +93 -9
  22. data/spec/bibtex_spec.rb +4 -4
  23. data/spec/cli_spec.rb +5 -0
  24. data/spec/codemeta_spec.rb +41 -31
  25. data/spec/crossref_spec.rb +47 -72
  26. data/spec/datacite_json_spec.rb +65 -0
  27. data/spec/datacite_spec.rb +67 -83
  28. data/spec/datacite_utils_spec.rb +9 -14
  29. data/spec/fixtures/datacite.json +49 -0
  30. data/spec/fixtures/datacite_software.json +18 -0
  31. data/spec/fixtures/vcr_cassettes/Bolognese_CLI/convert_from_id/datacite/to_datacite_json.yml +214 -0
  32. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/author_from_schema_org/with_id.yml +930 -0
  33. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/author_to_schema_org/with_id.yml +930 -0
  34. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/authors_as_string/author.yml +137 -860
  35. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/authors_as_string/no_author.yml +137 -860
  36. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/authors_as_string/single_author.yml +137 -860
  37. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/authors_as_string/with_organization.yml +137 -860
  38. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/from_schema_org/with_id.yml +930 -0
  39. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/get_name_identifier/has_ORCID.yml +155 -0
  40. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/get_name_identifier/has_no_ORCID.yml +134 -0
  41. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/get_one_author/has_familyName.yml +155 -0
  42. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/get_one_author/has_name_in_display-order.yml +186 -0
  43. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/get_one_author/has_name_in_display-order_with_ORCID.yml +177 -0
  44. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/get_one_author/has_name_in_sort-order.yml +173 -0
  45. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/get_one_author/is_organization.yml +207 -0
  46. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/is_personal_name_/has_comma.yml +207 -0
  47. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/is_personal_name_/has_family_name.yml +207 -0
  48. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/is_personal_name_/has_id.yml +207 -0
  49. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/is_personal_name_/has_no_info.yml +207 -0
  50. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/is_personal_name_/has_type_organization.yml +207 -0
  51. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/is_personal_name_/has_type_person.yml +207 -0
  52. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/sanitize/should_only_keep_specific_tags.yml +930 -0
  53. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/sanitize/should_remove_a_tags.yml +930 -0
  54. data/spec/fixtures/vcr_cassettes/Bolognese_Crossref/to_schema_org/with_id.yml +930 -0
  55. data/spec/fixtures/vcr_cassettes/Bolognese_Datacite/insert_related_identifiers/related_identifier.yml +173 -0
  56. data/spec/fixtures/vcr_cassettes/Bolognese_DataciteJson/get_metadata_as_bibtex/BlogPosting.yml +155 -0
  57. data/spec/schema_org_spec.rb +17 -14
  58. data/spec/utils_spec.rb +32 -2
  59. metadata +54 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bc292f3921b5634ec479a7e392fd8f9c476b5739
4
- data.tar.gz: 33a44a56720c036829596fad78a38674dc1bcdef
3
+ metadata.gz: d9c8e7a4955f749653d35917dae9f3a459c0f2da
4
+ data.tar.gz: b646899d5e3c5502320e754d25fc9cbfdafffc51
5
5
  SHA512:
6
- metadata.gz: 14e2ba1b16d38ffdf5bb839596b7c1b73e62fa2d2a35cca89d3c615801334f26c36dc302f87080a112ea5be6165614a49816a218242c86632422c4b937c7612b
7
- data.tar.gz: 5415c8c7283392900c4558d3b732099dc47216774207ce47d369221df3c2bf096abfdb0ca79e4a955b67135288c2d2ee80375b684c43a205bf163bb68b142785
6
+ metadata.gz: 892e8ccf9ad4354b93dca5b42d7fc099707007d2187dcaab5ad066ebf5357dc6ade3664e181408e05be3cfc07b8a77f86f1abe57f1348fd6a5d9fcfe5b293b06
7
+ data.tar.gz: 064b883d4eefefb064f4dc24fb5397a2c530f2dd1a52cc15319a52487820053d67b6a68dd94ae8a1dbd35dcb21d623e3a42e9539552172c0be536945edd4daf5
data/Gemfile.lock CHANGED
@@ -1,11 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bolognese (0.7.2)
4
+ bolognese (0.8)
5
5
  activesupport (~> 4.2, >= 4.2.5)
6
6
  bibtex-ruby (~> 4.1)
7
7
  builder (~> 3.2, >= 3.2.2)
8
8
  colorize (~> 0.8.1)
9
+ loofah (~> 2.0, >= 2.0.3)
9
10
  maremma (~> 3.5)
10
11
  namae (~> 0.10.2)
11
12
  nokogiri (~> 1.6, >= 1.6.8)
@@ -43,6 +44,8 @@ GEM
43
44
  json (2.0.3)
44
45
  latex-decode (0.2.2)
45
46
  unicode (~> 0.4)
47
+ loofah (2.0.3)
48
+ nokogiri (>= 1.5.9)
46
49
  maremma (3.5.1)
47
50
  activesupport (~> 4.2, >= 4.2.5)
48
51
  addressable (>= 2.3.6)
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
+ [![Identifier](https://img.shields.io/badge/doi-10.5438%2Fn138--z3mk-fca709.svg)](https://doi.org/10.5438/n138-z3mk)
2
+ [![Gem Version](https://badge.fury.io/rb/bolognese.svg)](https://badge.fury.io/rb/bolognese)
1
3
  [![Build Status](https://travis-ci.org/datacite/bolognese.svg?branch=master)](https://travis-ci.org/datacite/bolognese)
2
4
  [![Code Climate](https://codeclimate.com/github/datacite/bolognese/badges/gpa.svg)](https://codeclimate.com/github/datacite/bolognese)
3
5
  [![Test Coverage](https://codeclimate.com/github/datacite/bolognese/badges/coverage.svg)](https://codeclimate.com/github/datacite/bolognese/coverage)
4
6
 
5
- # Bolognese
7
+ # Bolognese: a Ruby library for conversion of DOI Metadata
6
8
 
7
9
  Ruby gem and command-line utility for conversion of DOI metadata from and to different metadata formats, including [schema.org](https://schema.org).
8
10
 
@@ -36,25 +38,32 @@ Bolognese reads and/or writes these metadata formats:
36
38
  <td>Yes</td>
37
39
  </tr>
38
40
  <tr>
39
- <td><a href='http://schema.org/'>Schema.org in JSON-LD</a></td>
40
- <td>schema_org</td>
41
- <td>application/vnd.schemaorg.ld+json</td>
42
- <td>Yes</td>
43
- <td>Yes</td>
41
+ <td><a href='https://api.datacite.org/'>DataCite JSON</a></td>
42
+ <td>datacite</td>
43
+ <td>application/vnd.datacite+json</td>
44
+ <td>Yes</td>
45
+ <td>Yes</td>
44
46
  </tr>
45
47
  <tr>
46
- <td><a href='https://codemeta.github.io/'>Codemeta</a></td>
47
- <td>codemeta</td>
48
- <td>application/ld+json</td>
49
- <td>Yes</td>
50
- <td>Yes</td>
48
+ <td><a href='http://schema.org/'>Schema.org in JSON-LD</a></td>
49
+ <td>schema_org</td>
50
+ <td>application/vnd.schemaorg.ld+json</td>
51
+ <td>Yes</td>
52
+ <td>Yes</td>
51
53
  </tr>
52
54
  <tr>
53
- <td><a href='http://en.wikipedia.org/wiki/BibTeX'>BibTeX</a></td>
54
- <td>bibtex</td>
55
- <td>application/x-bibtex</td>
56
- <td>Yes</td>
57
- <td>Yes</td>
55
+ <td><a href='https://codemeta.github.io/'>Codemeta</a></td>
56
+ <td>codemeta</td>
57
+ <td>application/ld+json</td>
58
+ <td>Yes</td>
59
+ <td>Yes</td>
60
+ </tr>
61
+ <tr>
62
+ <td><a href='http://en.wikipedia.org/wiki/BibTeX'>BibTeX</a></td>
63
+ <td>bibtex</td>
64
+ <td>application/x-bibtex</td>
65
+ <td>Yes</td>
66
+ <td>Yes</td>
58
67
  </tr>
59
68
  </tbody>
60
69
  </table>
data/bolognese.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.homepage = "https://github.com/datacite/bolognese"
9
9
  s.summary = "Ruby client library for conversion of DOI Metadata"
10
10
  s.date = Date.today
11
- s.description = "Convert DOI metadata to and from Crossref and DataCite XML, as well as schema.org/JSON-LD"
11
+ s.description = "Ruby gem and command-line utility for conversion of DOI metadata from and to different metadata formats, including schema.org."
12
12
  s.require_paths = ["lib"]
13
13
  s.version = Bolognese::VERSION
14
14
  s.extra_rdoc_files = ["README.md"]
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
17
17
  # Declary dependencies here, rather than in the Gemfile
18
18
  s.add_dependency 'maremma', '~> 3.5'
19
19
  s.add_dependency 'nokogiri', '~> 1.6', '>= 1.6.8'
20
+ s.add_dependency 'loofah', '~> 2.0', '>= 2.0.3'
20
21
  s.add_dependency 'builder', '~> 3.2', '>= 3.2.2'
21
22
  s.add_dependency 'activesupport', '~> 4.2', '>= 4.2.5'
22
23
  s.add_dependency 'bibtex-ruby', '~> 4.1'
data/codemeta.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "@context": "https://raw.githubusercontent.com/codemeta/codemeta/master/codemeta.jsonld",
3
+ "@type": "SoftwareSourceCode",
4
+ "@id": "https://doi.org/10.5438/N138-Z3MK",
5
+ "agents": {
6
+ "@id": "http://orcid.org/0000-0003-0077-4738",
7
+ "@type": "person",
8
+ "name": "Martin Fenner",
9
+ "affiliation": "DataCite",
10
+ "mustBeCited": true,
11
+ "isMaintainer": true,
12
+ "isRightsHolder": true
13
+ },
14
+ "identifier": "https://doi.org/10.5438/N138-Z3MK",
15
+ "codeRepository": "https://github.com/datacite/bolognese",
16
+ "dateCreated": "2017-02-13",
17
+ "datePublished": "2017-02-25",
18
+ "dateModified": "2017-02-25",
19
+ "description": "Ruby gem and command-line utility for conversion of DOI metadata from and to different metadata formats, including schema.org.",
20
+ "isAutomatedBuild": true,
21
+ "licenseId": "MIT",
22
+ "publisher": "DataCite",
23
+ "tags": [
24
+ "doi",
25
+ "metadata",
26
+ "crossref",
27
+ "datacite",
28
+ "schema.org",
29
+ "bibtex",
30
+ "codemeta"
31
+ ],
32
+ "title": "Bolognese: a Ruby library for conversion of DOI Metadata",
33
+ "programmingLanguage": {
34
+ "name": "Ruby",
35
+ "version": "≥ 2.3.3",
36
+ "URL": "https://www.ruby-lang.org"
37
+ },
38
+ "readme": "https://github.com/datacite/bolognese/blob/master/README.md"
39
+ }
data/lib/bolognese.rb CHANGED
@@ -4,14 +4,18 @@ require 'maremma'
4
4
  require 'postrank-uri'
5
5
  require 'bibtex'
6
6
  require 'colorize'
7
+ require 'loofah'
7
8
 
8
9
  require "bolognese/version"
9
10
  require "bolognese/metadata"
10
11
  require "bolognese/crossref"
11
12
  require "bolognese/datacite"
13
+ require "bolognese/datacite_json"
12
14
  require "bolognese/schema_org"
13
15
  require "bolognese/codemeta"
14
16
  require "bolognese/bibtex"
15
17
  require "bolognese/orcid"
16
18
  require "bolognese/cli"
17
19
  require "bolognese/string"
20
+ require "bolognese/array"
21
+ require "bolognese/whitelist_scrubber"
@@ -0,0 +1,11 @@
1
+ # turn array into hash or nil, depending on array size. Reverses Array.wrap,
2
+ # but uses self to allow chaining with Array.wrap
3
+ class Array
4
+ def unwrap
5
+ case self.length
6
+ when 0 then nil
7
+ when 1 then self.first
8
+ else self
9
+ end
10
+ end
11
+ end
@@ -4,25 +4,34 @@ module Bolognese
4
4
  module AuthorUtils
5
5
  # only assume personal name when using sort-order: "Turing, Alan"
6
6
  def get_one_author(author)
7
- orcid = get_name_identifier(author)
8
- author = author.fetch("creatorName", nil)
7
+ type = author.fetch("type", nil) && author.fetch("type").titleize
8
+ id = author.fetch("id", nil).presence || get_name_identifier(author)
9
+ name = author.fetch("creatorName", nil) ||
10
+ author.fetch("contributorName", nil) ||
11
+ author.fetch("name", nil)
12
+ name = cleanup_author(name)
13
+ given_name = author.fetch("givenName", nil)
14
+ family_name = author.fetch("familyName", nil)
9
15
 
10
- return { "name" => "" } if author.to_s.strip.blank?
16
+ author = { "type" => type || "Person",
17
+ "id" => id,
18
+ "name" => name,
19
+ "givenName" => given_name,
20
+ "familyName" => family_name }.compact
11
21
 
12
- author = cleanup_author(author)
13
- names = Namae.parse(author)
22
+ return author if family_name.present?
14
23
 
15
- if names.blank? || !is_personal_name?(author)
16
- { "@type" => "Agent",
17
- "@id" => orcid,
18
- "name" => author }.compact
19
- else
20
- name = names.first
24
+ if is_personal_name?(author)
25
+ names = Namae.parse(name)
26
+ parsed_name = names.first
21
27
 
22
- { "@type" => "Person",
23
- "@id" => orcid,
24
- "givenName" => name.given,
25
- "familyName" => name.family }.compact
28
+ { "type" => "Person",
29
+ "id" => id,
30
+ "name" => [parsed_name.given, parsed_name.family].join(" "),
31
+ "givenName" => parsed_name.given,
32
+ "familyName" => parsed_name.family }.compact
33
+ else
34
+ { "type" => type, "name" => name }.compact
26
35
  end
27
36
  end
28
37
 
@@ -37,15 +46,20 @@ module Bolognese
37
46
  end
38
47
 
39
48
  def is_personal_name?(author)
40
- return true if author.include?(",")
49
+ return false if author.fetch("type", "").downcase == "organization"
50
+ return true if author.fetch("type", "").downcase == "person" ||
51
+ author.fetch("id", "").start_with?("http://orcid.org") ||
52
+ author.fetch("familyName", "").present? ||
53
+ author.fetch("name", "").include?(",")
41
54
 
42
55
  # lookup given name
43
56
  #::NameDetector.name_exists?(author.split.first)
57
+ false
44
58
  end
45
59
 
46
60
  # parse array of author strings into CSL format
47
61
  def get_authors(authors)
48
- Array(authors).map { |author| get_one_author(author) }
62
+ Array.wrap(authors).map { |author| get_one_author(author) }.unwrap
49
63
  end
50
64
 
51
65
  # parse nameIdentifier from DataCite
@@ -61,12 +75,12 @@ module Bolognese
61
75
 
62
76
  def authors_as_string(authors)
63
77
  Array.wrap(authors).map do |a|
64
- if a["@type"] == "organization"
65
- "{" + a["name"] + "}"
66
- elsif a["familyName"].present?
78
+ if a["familyName"].present?
67
79
  [a["familyName"], a["givenName"]].join(", ")
68
- else
80
+ elsif a["type"] == "Person"
69
81
  a["name"]
82
+ elsif a["name"].present?
83
+ "{" + a["name"] + "}"
70
84
  end
71
85
  end.join(" and ").presence
72
86
  end
@@ -70,7 +70,7 @@ module Bolognese
70
70
  end
71
71
  end
72
72
 
73
- def name
73
+ def title
74
74
  metadata.title
75
75
  end
76
76
 
@@ -88,7 +88,7 @@ module Bolognese
88
88
 
89
89
  def is_part_of
90
90
  if metadata.journal.present?
91
- { "@type" => "Periodical",
91
+ { "type" => "Periodical",
92
92
  "name" => metadata.journal.to_s,
93
93
  "issn" => metadata.issn.to_s.presence }.compact
94
94
  else
@@ -97,11 +97,11 @@ module Bolognese
97
97
  end
98
98
 
99
99
  def description
100
- metadata.field?(:abstract) && metadata.abstract.to_s.presence
100
+ { "text" => metadata.field?(:abstract) && metadata.abstract.to_s.presence }
101
101
  end
102
102
 
103
103
  def license
104
- metadata.field?(:copyright) && metadata.copyright.to_s.presence
104
+ { "id" => metadata.field?(:copyright) && metadata.copyright.to_s.presence }
105
105
  end
106
106
  end
107
107
  end
@@ -56,7 +56,7 @@ module Bolognese
56
56
  Bolognese::Bibtex::SO_TO_BIB_TRANSLATIONS[type] || "misc"
57
57
  end
58
58
 
59
- def name
59
+ def title
60
60
  metadata.fetch("title", nil)
61
61
  end
62
62
 
@@ -65,20 +65,21 @@ module Bolognese
65
65
  end
66
66
 
67
67
  def author
68
- arr = Array.wrap(metadata.fetch("agents", nil)).map { |a| a.slice("@type", "@id", "name") }
69
- array_unwrap(arr)
68
+ authors = from_schema_org(Array.wrap(metadata.fetch("agents", nil)))
69
+ get_authors(authors)
70
70
  end
71
71
 
72
72
  def editor
73
- Array(metadata.fetch("editor", nil)).map { |a| a.except("name") }.presence
73
+ editors = from_schema_org(Array.wrap(metadata.fetch("editor", nil)))
74
+ get_authors(editors)
74
75
  end
75
76
 
76
77
  def description
77
- metadata.fetch("description", nil)
78
+ { "text" => metadata.fetch("description", nil) }
78
79
  end
79
80
 
80
81
  def license
81
- metadata.fetch("license", nil)
82
+ { "id" => metadata.fetch("license", nil) }
82
83
  end
83
84
 
84
85
  def version
@@ -122,13 +123,7 @@ module Bolognese
122
123
  end
123
124
 
124
125
  def publisher
125
- p = metadata.fetch("publisher", nil)
126
- if p.is_a?(Hash)
127
- p
128
- elsif p.is_a?(String)
129
- { "@type" => "Organization",
130
- "name" => p }
131
- end
126
+ metadata.fetch("publisher", nil)
132
127
  end
133
128
 
134
129
  def container_title
@@ -68,6 +68,7 @@ module Bolognese
68
68
  elsif id.present?
69
69
  response = Maremma.get(id, accept: "application/vnd.crossref.unixref+xml", host: true, raw: true)
70
70
  @raw = response.body.fetch("data", nil)
71
+ @raw = Nokogiri::XML(@raw, &:noblanks).to_s if @raw.present?
71
72
  end
72
73
  end
73
74
 
@@ -132,7 +133,7 @@ module Bolognese
132
133
  CR_TO_BIB_TRANSLATIONS[additional_type] || "misc"
133
134
  end
134
135
 
135
- def name
136
+ def title
136
137
  parse_attributes(bibliographic_metadata.dig("titles", "title"))
137
138
  end
138
139
 
@@ -172,23 +173,21 @@ module Bolognese
172
173
 
173
174
  def people(contributor_role)
174
175
  person = bibliographic_metadata.dig("contributors", "person_name")
175
- arr = Array.wrap(person).select { |a| a["contributor_role"] == contributor_role }.map do |a|
176
- { "@type" => "Person",
177
- "@id" => parse_attributes(a["ORCID"]),
176
+ Array.wrap(person).select { |a| a["contributor_role"] == contributor_role }.map do |a|
177
+ { "type" => "Person",
178
+ "id" => parse_attributes(a["ORCID"]),
179
+ "name" => [a["given_name"], a["surname"]].join(" "),
178
180
  "givenName" => a["given_name"],
179
181
  "familyName" => a["surname"] }.compact
180
- end
181
- array_unwrap(arr)
182
+ end.unwrap
182
183
  end
183
184
 
184
185
  def funder
185
186
  fundref = Array.wrap(program_metadata).find { |a| a["name"] == "fundref" } || {}
186
- arr = Array.wrap(fundref.fetch("assertion", [])).select { |a| a["name"] == "fundgroup" }.map do |f|
187
- { "@type" => "Organization",
188
- "@id" => normalize_id(f.dig("assertion", "assertion", "__content__")),
187
+ Array.wrap(fundref.fetch("assertion", [])).select { |a| a["name"] == "fundgroup" }.map do |f|
188
+ { "id" => normalize_id(f.dig("assertion", "assertion", "__content__")),
189
189
  "name" => f.dig("assertion", "__content__").strip }.compact
190
- end
191
- array_unwrap(arr)
190
+ end.unwrap
192
191
  end
193
192
 
194
193
  def date_published
@@ -215,7 +214,7 @@ module Bolognese
215
214
 
216
215
  def is_part_of
217
216
  if journal_metadata.present?
218
- { "@type" => "Periodical",
217
+ { "type" => "Periodical",
219
218
  "name" => journal_metadata["full_title"],
220
219
  "issn" => parse_attributes(journal_metadata.fetch("issn", nil)) }.compact
221
220
  else
@@ -229,20 +228,23 @@ module Bolognese
229
228
 
230
229
  alias_method :journal, :container_title
231
230
 
232
- def citation
233
- citations = bibliographic_metadata.dig("citation_list", "citation")
234
- Array.wrap(citations).map do |c|
235
- { "@type" => "CreativeWork",
236
- "@id" => normalize_id(c["doi"]),
231
+ def related_identifier(relation_type: nil)
232
+ references
233
+ end
234
+
235
+ def references
236
+ refs = bibliographic_metadata.dig("citation_list", "citation")
237
+ Array.wrap(refs).map do |c|
238
+ { "id" => normalize_id(c["doi"]),
239
+ "relationType" => "Cites",
237
240
  "position" => c["key"],
238
241
  "name" => c["article_title"],
239
242
  "datePublished" => c["cYear"] }.compact
240
- end.presence
243
+ end.unwrap
241
244
  end
242
245
 
243
246
  def provider
244
- { "@type" => "Organization",
245
- "name" => "Crossref" }
247
+ "Crossref"
246
248
  end
247
249
  end
248
250
  end