Class: Relaton::Doi::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/relaton/doi/parser.rb

Constant Summary collapse

COUNTRIES =
%w[USA].freeze
TYPES =
{
  "book-chapter" => "inbook",
  "book-part" => "inbook",
  "book-section" => "inbook",
  "book-series" => "book",
  "book-set" => "book",
  "book-track" => "inbook",
  "component" => "misc",
  "database" => "dataset",
  "dissertation" => "thesis",
  "edited-book" => "book",
  "grant" => "misc",
  "journal-article" => "article",
  "journal-issue" => "article",
  "journal-volume" => "journal",
  "monograph" => "book",
  "other" => "misc",
  "peer-review" => "article",
  "posted-content" => "dataset",
  "proceedings-article" => "inproceedings",
  "proceedings-series" => "proceedings",
  "reference-book" => "book",
  "reference-entry" => "inbook",
  "report-component" => "techreport",
  "report-series" => "techreport",
  "report" => "techreport",
}.freeze
REALATION_TYPES =
{
  "is-cited-by" => "isCitedIn",
  "belongs-to" => "related",
  "is-child-of" => "includedIn",
  "is-expression-of" => "expressionOf",
  "has-expression" => "hasExpression",
  "is-manifestation-of" => "manifestationOf",
  "is-manuscript-of" => "draftOf",
  "has-manuscript" => "hasDraft",
  "is-preprint-of" => "draftOf",
  "has-preprint" => "hasDraft",
  "is-replaced-by" => "obsoletedBy",
  "replaces" => "obsoletes",
  "is-translation-of" => "translatedFrom",
  "has-translation" => "hasTranslation",
  "is-version-of" => "editionOf",
  "has-version" => "hasEdition",
  "is-based-on" => "updates",
  "is-basis-for" => "updatedBy",
  "is-comment-on" => "commentaryOf",
  "has-comment" => "hasCommentary",
  "is-continued-by" => "hasSuccessor",
  "continues" => "successorOf",
  "is-derived-from" => "derives",
  "has-derivation" => "derivedFrom",
  "is-documented-by" => "describedBy",
  "documents" => "describes",
  "is-part-of" => "partOf",
  "has-part" => "hasPart",
  "is-review-of" => "reviewOf",
  "has-review" => "hasReview",
  "references" => "cites",
  "is-referenced-by" => "isCitedIn",
  "requires" => "hasComplement",
  "is-required-by" => "complementOf",
  "is-supplement-to" => "complementOf",
  "is-supplemented-by" => "hasComplement",
}.freeze
ATTRS =
%i[type fetched title docidentifier date source abstract contributor place
ext relation extent series medium].freeze
CROSSREF_API_URL =
"https://api.crossref.org/works?query=%{query}&filter=%{filter}".freeze
MAX_RETRIES =
3

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(src) ⇒ Parser

Initialize instance.

Parameters:

  • src (Hash)

    The source hash.



84
85
86
87
# File 'lib/relaton/doi/parser.rb', line 84

def initialize(src)
  @src = src
  @item = {}
end

Class Method Details

.parse(src) ⇒ Bib::ItemData

Initialize instance and parse the source hash.

Parameters:

  • src (Hash)

    The source hash.

Returns:

  • (Bib::ItemData)

    The bibitem.



96
97
98
# File 'lib/relaton/doi/parser.rb', line 96

def self.parse(src)
  new(src).parse
end

Instance Method Details

#author_investigatorsArray<Bib::Contributor>

Create authors investigators from the source hash.

Returns:

  • (Array<Bib::Contributor>)

    The authors investigators.



332
333
334
335
336
337
# File 'lib/relaton/doi/parser.rb', line 332

def author_investigators
  Array(@src["project"]).reduce([]) do |memo, proj|
    memo + create_investigators(proj, "lead-investigator") +
      create_investigators(proj, "investigator")
  end
end

#authors_editors_translatorsArray<Bib::Contributor>

Create authors editors translators from the source hash.

Returns:

  • (Array<Bib::Contributor>)

    The authors editors translators.



359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/relaton/doi/parser.rb', line 359

def authors_editors_translators
  %w[author editor translator].each_with_object([]) do |type, a|
    @src[type]&.each do |c|
      contrib = if c["family"]
                  create_person(c)
                else
                  create_org(str_cleanup(c["name"]))
                end
      a << contributor(contrib, type)
    end
  end
end

#completename(person) ⇒ Bib::LocalizedString

Create a complete name from a person hash.

Parameters:

  • person (Hash)

    The person hash.

Returns:

  • (Bib::LocalizedString)

    The complete name.



593
594
595
596
597
# File 'lib/relaton/doi/parser.rb', line 593

def completename(person)
  return unless person["name"]

  Bib::LocalizedString.new(content: person["name"], language: "en", script: "Latn")
end

#contribs_from_parent(contribs) ⇒ Array<Bib::Contributor>

Fetch authors and editors from parent if they are not present in the book part.

Parameters:

  • contribs (Array<Bib::Contributor>)

    present contributors

Returns:

  • (Array<Bib::Contributor>)

    contributors with authors and editors from parent



379
380
381
382
383
384
385
386
387
# File 'lib/relaton/doi/parser.rb', line 379

def contribs_from_parent(contribs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
  return [] unless %w[inbook inproceedings dataset].include?(parse_type) && @src["container-title"]

  has_authors = contribs.any? { |c| c.role&.any? { |r| r.type == "author" } }
  has_editors = contribs.any? { |c| c.role&.any? { |r| r.type == "editor" } }
  return [] if has_authors && has_editors

  create_authors_editors(has_authors, "author")
end

#contributor(entity, type, descriprion = nil) ⇒ Bib::Contributor

Create contributor from an entity and a role type.

Parameters:

  • entity (Bib::Person, Bib::Organization)

    The entity.

  • type (String)

    The role type.

Returns:

  • (Bib::Contributor)

    The contributor.



501
502
503
504
505
506
507
508
509
# File 'lib/relaton/doi/parser.rb', line 501

def contributor(entity, type, descriprion = nil)
  desc = descriprion ? [Bib::LocalizedMarkedUpString.new(content: descriprion)] : nil
  role = [Bib::Contributor::Role.new(type: type, description: desc)]
  if entity.is_a?(Bib::Person)
    Bib::Contributor.new(role: role, person: entity)
  else
    Bib::Contributor.new(role: role, organization: entity)
  end
end

#create_affiliation(person) ⇒ Array<Bib::Affiliation>

Create person affiliations from a person hash.

Parameters:

  • person (Hash)

    The person hash.

Returns:

  • (Array<Bib::Affiliation>)

    The affiliations.



533
534
535
536
537
# File 'lib/relaton/doi/parser.rb', line 533

def create_affiliation(person)
  (person["affiliation"] || []).map do |a|
    Bib::Affiliation.new organization: create_org(a["name"])
  end
end

#create_authors_editors(has, type) ⇒ Array<Bib::Contributor>

Create authors and editors from parent item.

Parameters:

  • has (Boolean)

    true if authors or editors are present in the book part

  • type (String)

    “author” or “editor”

Returns:

  • (Array<Bib::Contributor>)

    authors or editors



411
412
413
414
415
# File 'lib/relaton/doi/parser.rb', line 411

def create_authors_editors(has, type)
  return [] if has || !parent_item

  Array(parent_item[type]).map { |a| contributor(create_person(a), type) }
end

#create_bibitem(doi, bibitem) ⇒ Bib::ItemData

Create a bibitem from the bibitem hash.

Parameters:

  • doi (String)

    The DOI.

  • bibitem (Hash)

    The bibitem hash.

Returns:

  • (Bib::ItemData)

    The bibitem.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/relaton/doi/parser.rb', line 118

def create_bibitem(doi, bibitem) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
  doctype_content = @src["type"]
  case doi
  when /\/nist/
    bibitem[:ext] = Nist::Ext.new(doctype: Nist::Doctype.new(content: doctype_content))
    Nist::ItemData.new(**bibitem)
  when /\/rfc\d+/
    bibitem[:ext] = Ietf::Ext.new(doctype: Ietf::Doctype.new(content: doctype_content))
    Ietf::ItemData.new(**bibitem)
  when /\/0026-1394\//
    bibitem[:ext] = Bipm::Ext.new(doctype: Bipm::Doctype.new(content: doctype_content))
    Bipm::ItemData.new(**bibitem)
  when /\/ieee/
    bibitem[:ext] = Ieee::Ext.new(doctype: Ieee::Doctype.new(content: doctype_content))
    Ieee::ItemData.new(**bibitem)
  else Bib::ItemData.new(**bibitem)
  end
end

#create_enabler(name) ⇒ Bib::Contributor

Create enabler contributor with type “enabler”.

Parameters:

  • name (String)

    The funder name.

Returns:

  • (Bib::Contributor)

    The enabler contributor.



489
490
491
# File 'lib/relaton/doi/parser.rb', line 489

def create_enabler(name)
  contributor(create_org(name), "enabler")
end

#create_investigators(project, type) ⇒ Array<Bib::Contributor>

Create investigators from the project.

Parameters:

  • project (Hash)

    The project hash.

  • type (String)

    The investigator type. “lead-investigator” or “investigator”.

Returns:

  • (Array<Bib::Contributor>)

    The investigators.



347
348
349
350
351
352
# File 'lib/relaton/doi/parser.rb', line 347

def create_investigators(project, type)
  description = type.gsub("-", " ")
  Array(project[type]).map do |inv|
    contributor(create_person(inv), "author", description)
  end
end

#create_org(name, abbreviation = nil) ⇒ Bib::Organization

Create an organization with properly typed name and abbreviation.

Parameters:

  • name (String)

    The organization name.

  • abbreviation (String, nil) (defaults to: nil)

    The organization abbreviation.

Returns:

  • (Bib::Organization)

    The organization.



450
451
452
453
454
# File 'lib/relaton/doi/parser.rb', line 450

def create_org(name, abbreviation = nil)
  n = [Bib::TypedLocalizedString.new(content: name)]
  a = abbreviation ? Bib::LocalizedString.new(content: abbreviation) : nil
  Bib::Organization.new name: n, abbreviation: a
end

#create_person(person) ⇒ Bib::Person

Create a person from a person hash.

Parameters:

  • person (Hash)

    The person hash.

Returns:

  • (Bib::Person)

    The person.



518
519
520
521
522
523
524
# File 'lib/relaton/doi/parser.rb', line 518

def create_person(person)
  Bib::Person.new(
    name: create_person_name(person),
    affiliation: create_affiliation(person),
    identifier: person_id(person),
  )
end

#create_person_name(person) ⇒ Bib::FullName

Create a person full name from a person hash.

Parameters:

  • person (Hash)

    The person hash.

Returns:

  • (Bib::FullName)

    The full name.



546
547
548
549
550
551
552
553
# File 'lib/relaton/doi/parser.rb', line 546

def create_person_name(person)
  surname = titlecase(person["family"])
  sn = Bib::LocalizedString.new(content: surname, language: "en", script: "Latn")
  Bib::FullName.new(
    surname: sn, forename: forename(person), addition: nameaddition(person),
    completename: completename(person), prefix: nameprefix(person)
  )
end

#create_title(title, type = "main") ⇒ Bib::Title

Create a title from the title and type.

Parameters:

  • title (String)

    The title content.

  • type (String) (defaults to: "main")

    The title type. Defaults to “main”.

Returns:

  • (Bib::Title)

    The title.



211
212
213
214
# File 'lib/relaton/doi/parser.rb', line 211

def create_title(title, type = "main")
  cnt = str_cleanup title
  Bib::Title.new type: type, content: cnt, script: "Latn"
end

#date_type(type) ⇒ String

Join date parts into a string.

Parameters:

  • type (String)

    The date type.

Returns:

  • (String)

    The date string.



270
271
272
# File 'lib/relaton/doi/parser.rb', line 270

def date_type(type)
  @src[type]["date-parts"][0].map { |d| d.to_s.rjust(2, "0") }.join "-"
end

#fetch_crossref(query:, filter:) ⇒ Array<Hash>?

Fetch data from Crossref API with retry logic.

Parameters:

  • query (String)

    The query string.

  • filter (String)

    The filter string.

Returns:

  • (Array<Hash>, nil)

    Items array from response or nil for 4xx responses.

Raises:

  • (Relaton::RequestError)

    If request fails after retries.



804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
# File 'lib/relaton/doi/parser.rb', line 804

def fetch_crossref(query:, filter:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
  url = format(CROSSREF_API_URL, query: query, filter: filter)
  retries = 0
  begin
    resp = Crossref.agent.get url
    JSON.parse(resp.body).dig("message", "items")
  rescue Mechanize::ResponseCodeError => e
    return nil if e.response_code.start_with?("4")

    raise Relaton::RequestError, "Crossref request failed: #{e.response_code} #{e.page.body}"
  rescue Net::HTTP::Persistent::Error, Net::HTTPError, Errno::ECONNREFUSED => e
    retries += 1
    retry if retries <= MAX_RETRIES
    raise Relaton::RequestError, "Crossref network error after #{MAX_RETRIES} retries: #{e.message}"
  rescue JSON::ParserError => e
    raise Relaton::RequestError, "Crossref JSON parsing error: #{e.message}"
  end
end

#fetch_locationString?

Fetch location from container.

Returns:

  • (String, nil)

    The location.



669
670
671
672
673
674
675
676
677
678
# File 'lib/relaton/doi/parser.rb', line 669

def fetch_location
  title = @item[:title].first&.content
  qparts = [title, fetch_year, @src["publisher"]]
  query = CGI.escape qparts.compact.join("+").gsub(" ", "+")
  filter = "type:#{%w[book-chapter book-part book-section book-track].join(',type:')}"
  items = fetch_crossref(query: query, filter: filter)
  items&.detect do |i|
    i["publisher-location"] && i["container-title"].include?(title)
  end&.dig("publisher-location")
end

#fetch_yearString

Fetch year from the source hash.

Returns:

  • (String)

    The year.



738
739
740
741
# File 'lib/relaton/doi/parser.rb', line 738

def fetch_year
  d = @src["published"] || @src["approved"] || @src["created"]
  d["date-parts"][0][0]
end

#forename(person) ⇒ Array<Bib::FullNameType::Forename>

Create a forename from a person hash.

Parameters:

  • person (Hash)

    The person hash.

Returns:

  • (Array<Bib::FullNameType::Forename>)

    The forename.



606
607
608
609
610
611
# File 'lib/relaton/doi/parser.rb', line 606

def forename(person)
  return [] unless person["given"]

  fname = titlecase(person["given"])
  [Bib::FullNameType::Forename.new(content: fname, language: "en", script: "Latn")]
end

#included_in_relationArray<Bib::Relation>

Create included in relation.

Returns:

  • (Array<Bib::Relation>)

    The relations.



719
720
721
722
723
724
725
726
727
728
729
730
731
# File 'lib/relaton/doi/parser.rb', line 719

def included_in_relation
  types = %w[
    book book-chapter book-part book-section book-track dataset journal-issue
    journal-value proceedings-article reference-entry report-component
  ]
  return [] unless @src["container-title"] && types.include?(@src["type"])

  @src["container-title"].map do |ct|
    contrib = create_authors_editors false, "editor"
    bib = Bib::ItemBase.new(title: [Bib::Title.new(content: ct)], contributor: contrib)
    Bib::Relation.new(type: "includedIn", bibitem: bib)
  end
end

#issn_type(type, id) ⇒ String

Create an ISSN type if it’s an ISSN ID.

Parameters:

  • type (String)

    identifier type

  • id (String)

    identifier

Returns:

  • (String)

    identifier type



239
240
241
242
243
244
# File 'lib/relaton/doi/parser.rb', line 239

def issn_type(type, id)
  return type unless type == "ISSN"

  t = @src["issn-type"]&.find { |it| it["value"] == id }&.dig("type")
  t ? "issn.#{t}" : type.downcase
end

#main_sub_titlesArray<Bib::Title>

Parse main and subtitle from the source hash.

Returns:

  • (Array<Bib::Title>)

    The titles.



185
186
187
188
189
190
# File 'lib/relaton/doi/parser.rb', line 185

def main_sub_titles
  title = @src["title"].map { |t| create_title t }
  Array(@src["subtitle"]).each { |t| title << create_title(t, "subtitle") }
  Array(@src["short-title"]).each { |t| title << create_title(t, "short") }
  title
end

#nameaddition(person) ⇒ Array<Bib::LocalizedString>

Create an addition from a person hash.

Parameters:

  • person (Hash)

    The person hash.

Returns:

  • (Array<Bib::LocalizedString>)

    The addition.



620
621
622
623
624
# File 'lib/relaton/doi/parser.rb', line 620

def nameaddition(person)
  return [] unless person["suffix"]

  [Bib::LocalizedString.new(content: person["suffix"], language: "en", script: "Latn")]
end

#nameprefix(person) ⇒ Array<Bib::LocalizedString>

Create a person name prefix from a person hash.

Parameters:

  • person (Hash)

    The person hash.

Returns:

  • (Array<Bib::LocalizedString>)

    The name prefix.



580
581
582
583
584
# File 'lib/relaton/doi/parser.rb', line 580

def nameprefix(person)
  return [] unless person["prefix"]

  [Bib::LocalizedString.new(content: person["prefix"], language: "en", script: "Latn")]
end

#org_aurhorizerArray<Bib::Contributor>

Parse authorizer contributor from the source hash.

Returns:

  • (Array<Bib::Contributor>)

    The authorizer contributor.



461
462
463
464
465
466
467
# File 'lib/relaton/doi/parser.rb', line 461

def org_aurhorizer
  return [] unless @src["standards-body"]

  name, acronym = @src["standards-body"].values_at("name", "acronym")
  org = create_org(name, acronym)
  [contributor(org, "authorizer")]
end

#org_enablerArray<Bib::Contributor>

Parse enabler contributor from the source hash.

Returns:

  • (Array<Bib::Contributor>)

    The enabler contributor.



474
475
476
477
478
479
480
# File 'lib/relaton/doi/parser.rb', line 474

def org_enabler
  Array(@src["project"]).each_with_object([]) do |proj, memo|
    proj["funding"].each do |f|
      memo << create_enabler(f.dig("funder", "name"))
    end
  end + Array(@src["funder"]).map { |f| create_enabler f["name"] }
end

#org_publisherBib::Organization

Cerate an organization publisher from the source hash.

Returns:

  • (Bib::Organization)

    The organization.



422
423
424
425
426
427
428
429
# File 'lib/relaton/doi/parser.rb', line 422

def org_publisher
  pbr = @src["institution"]&.detect do |i|
    @src["publisher"].include?(i["name"]) ||
      i["name"].include?(@src["publisher"])
  end
  a = pbr["acronym"]&.first if pbr
  create_org(str_cleanup(@src["publisher"]), a)
end

#parent_itemHash?

Fetch parent item from Crossref.

Returns:

  • (Hash, nil)

    parent item



394
395
396
397
398
399
400
401
# File 'lib/relaton/doi/parser.rb', line 394

def parent_item
  @parent_item ||= begin
    query = CGI.escape [@src["container-title"][0], fetch_year].compact.join("+")
    filter = "type:#{%w[book book-set edited-book monograph reference-book].join ',type:'}"
    items = fetch_crossref(query: query, filter: filter)
    items&.detect { |i| i["title"].include? @src["container-title"][0] }
  end
end

#parseBib::ItemData

Parse the source hash.

Returns:

  • (Bib::ItemData)

    The bibitem.



105
106
107
108
# File 'lib/relaton/doi/parser.rb', line 105

def parse
  ATTRS.each { |m| @item[m] = send "parse_#{m}" }
  create_bibitem @src["DOI"], @item
end

#parse_abstractArray<Bib::LocalizedMarkedUpString>

Parse abstract from the source hash.

Returns:

  • (Array<Bib::LocalizedMarkedUpString>)

    The abstract.



303
304
305
306
307
308
309
310
311
# File 'lib/relaton/doi/parser.rb', line 303

def parse_abstract
  return [] unless @src["abstract"]

  content = @src["abstract"]
  abstract = Bib::Abstract.new(
    content: content, language: "en", script: "Latn",
  )
  [abstract]
end

#parse_contributorArray<Bib::Contributor>

Parse contributors from the source hash.

Returns:

  • (Array<Bib::Contributor>)

    The contributors.



318
319
320
321
322
323
324
325
# File 'lib/relaton/doi/parser.rb', line 318

def parse_contributor
  contribs = author_investigators
  contribs += authors_editors_translators
  contribs += contribs_from_parent(contribs)
  contribs << contributor(org_publisher, "publisher")
  contribs += org_aurhorizer
  contribs + org_enabler
end

#parse_dateArray<Bib::Date>

Parce dates from the source hash.

Returns:

  • (Array<Bib::Date>)

    The dates.



251
252
253
254
255
256
257
258
259
260
261
# File 'lib/relaton/doi/parser.rb', line 251

def parse_date # rubocop:disable Metrics/CyclomaticComplexity
  dates = %w[issued published approved].each_with_object([]) do |type, obj|
    next unless @src.dig(type, "date-parts")&.first&.compact&.any?

    obj << Bib::Date.new(type: type, at: date_type(type))
  end
  if dates.none?
    dates << Bib::Date.new(type: "created", at: date_type("created"))
  end
  dates
end

#parse_docidentifierArray<Bib::Docidentifier>

Parse a docidentifier from the source hash.

Returns:

  • (Array<Bib::Docidentifier>)

    The docidentifier.



221
222
223
224
225
226
227
228
229
# File 'lib/relaton/doi/parser.rb', line 221

def parse_docidentifier
  %w[DOI ISBN ISSN].each_with_object([]) do |type, obj|
    prm = type == "DOI"
    Array(@src[type]).each do |id|
      t = issn_type(type, id)
      obj << Bib::Docidentifier.new(type: t, content: id, primary: prm)
    end
  end
end

#parse_extBib::Ext

Parse the ext element with doctype.

Returns:

  • (Bib::Ext)

    The ext element.



151
152
153
# File 'lib/relaton/doi/parser.rb', line 151

def parse_ext
  Bib::Ext.new doctype: Bib::Doctype.new(content: @src["type"])
end

#parse_extentArray<Bib::Extent>

Parse an extent from the source hash.

Returns:

  • (Array<Bib::Extent>)

    The extent.



748
749
750
751
752
753
754
755
756
757
# File 'lib/relaton/doi/parser.rb', line 748

def parse_extent # rubocop:disable Metrics/AbcSize
  extent = []
  extent << Bib::Locality.new(type: "volume", reference_from: @src["volume"]) if @src["volume"]
  extent << Bib::Locality.new(type: "issue", reference_from: @src["issue"]) if @src["issue"]
  if @src["page"]
    from, to = @src["page"].split("-")
    extent << Bib::Locality.new(type: "page", reference_from: from, reference_to: to)
  end
  extent.any? ? [Bib::Extent.new(locality: extent)] : []
end

#parse_fetchedString

Parse the fetched date.

Returns:

  • (String)

    The fetched date.



160
161
162
# File 'lib/relaton/doi/parser.rb', line 160

def parse_fetched
  Date.today.to_s
end

#parse_mediumBib::Medium?

Parse a medium from the source hash.

Returns:

  • (Bib::Medium, nil)

    The medium.



787
788
789
790
791
792
# File 'lib/relaton/doi/parser.rb', line 787

def parse_medium
  genre = @src["degree"]&.first
  return unless genre

  Bib::Medium.new genre: genre
end

#parse_placeArray<Bib::Place>

Parse a place from the source hash.

Returns:

  • (Array<Bib::Place>)

    The place.



644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# File 'lib/relaton/doi/parser.rb', line 644

def parse_place # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
  pub_location = @src["publisher-location"] || fetch_location
  return [] unless pub_location

  pls1, pls2 = pub_location.split(", ")
  pls1 = str_cleanup pls1
  pls2 &&= str_cleanup pls2
  if COUNTRIES.include? pls2
    country = Bib::Place::RegionType.new(content: pls2)
    [Bib::Place.new(city: pls1, country: [country])]
  elsif pls2 && pls2 == pls2&.upcase
    region = Bib::Place::RegionType.new(content: pls2)
    [Bib::Place.new(city: pls1, region: [region])]
  elsif pls1 == pls2 || pls2.nil? || pls2.empty?
    [Bib::Place.new(city: pls1)]
  else
    [Bib::Place.new(city: pls1), Bib::Place.new(city: pls2)]
  end
end

#parse_relationArray<Bib::Relation>

Parse relations from the source hash.

Returns:

  • (Array<Bib::Relation>)

    The relations.



685
686
687
688
689
690
691
692
693
694
695
696
697
# File 'lib/relaton/doi/parser.rb', line 685

def parse_relation # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
  rels = included_in_relation
  @src["relation"].each_with_object(rels) do |(k, v), a|
    type, desc = relation_type k
    Array(v).each do |r|
      rel_item = Crossref.get_by_id r["id"]
      title = rel_item["title"].map { |t| create_title t }
      docid = Bib::Docidentifier.new(content: r["id"], type: "DOI")
      bib = create_bibitem r["id"], title: title, docidentifier: [docid]
      a << Bib::Relation.new(type: type, description: desc, bibitem: bib)
    end
  end
end

#parse_seriesArray<Bib::Series>

Parse a series from the source hash.

Returns:

  • (Array<Bib::Series>)

    The series.



764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
# File 'lib/relaton/doi/parser.rb', line 764

def parse_series # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
  types = %w[inbook incollection inproceedings]
  return [] if !@src["container-title"] || types.include?(@item[:type]) || @src["type"] == "report-component"

  con_ttl = if main_sub_titles.any? || project_titles.any?
              @src["container-title"]
            elsif @src["container-title"].size > 1
              sct = @src["short-container-title"]&.last
              abbrev = Bib::LocalizedString.new(content: sct) if sct
              @src["container-title"][-1..-1]
            else []
            end
  con_ttl.map do |ct|
    title = Bib::Title.new content: ct
    Bib::Series.new title: [title], abbreviation: abbrev
  end
end

#parse_sourceArray<Bib::Uri>

Parse source URIs from the source hash.

Returns:

  • (Array<Bib::Uri>)

    The source URIs.



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/relaton/doi/parser.rb', line 279

def parse_source # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
  disprefered_links = %w[similarity-checking text-mining]
  links = []
  if @src["URL"]
    links << Bib::Uri.new(type: "DOI", content: @src["URL"])
  end
  [@src["link"], @src.dig("resource", "primary")].flatten.compact.each do |l|
    next if disprefered_links.include? l["intended-application"]

    type =  case l["URL"]
            when /\.pdf$/ then "pdf"
            # when /\/rfc\d+$|iopscience\.iop\.org|ieeexplore\.ieee\.org/
            else "src"
            end
    links << Bib::Uri.new(type: type, content: l["URL"]) # if type
  end
  links
end

#parse_titleArray<Bib::Title>

Parse titles from the source hash.

Returns:

  • (Array<Bib::Title>)

    The titles.



169
170
171
172
173
174
175
176
177
178
# File 'lib/relaton/doi/parser.rb', line 169

def parse_title # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
  if @src["title"].is_a?(Array) && @src["title"].any?
    main_sub_titles
  elsif @src["project"].is_a?(Array) && @src["project"].any?
    project_titles
  elsif @src["container-title"].is_a?(Array) && @src["container-title"].size > 1
    @src["container-title"][0..-2].map { |t| create_title t }
  else []
  end
end

#parse_typeString

Parse the type.

Returns:

  • (String)

    The type.



142
143
144
# File 'lib/relaton/doi/parser.rb', line 142

def parse_type
  TYPES[@src["type"]] || @src["type"]
end

#person_id(person) ⇒ Array<Bib::Person::Identifier>

Create a person identifier from a person hash.

Parameters:

  • person (Hash)

    The person hash.

Returns:

  • (Array<Bib::Person::Identifier>)

    The person identifier.



633
634
635
636
637
# File 'lib/relaton/doi/parser.rb', line 633

def person_id(person)
  return [] unless person["ORCID"]

  [Bib::Person::Identifier.new(type: "orcid", content: person["ORCID"])]
end

#project_titlesArray<Bib::Title>

Fetch titles from the projects.

Returns:

  • (Array<Bib::Title>)

    The titles.



197
198
199
200
201
# File 'lib/relaton/doi/parser.rb', line 197

def project_titles
  Array(@src["project"]).reduce([]) do |memo, proj|
    memo + Array(proj["project-title"]).map { |t| create_title t["title"] }
  end
end

#relation_type(crtype) ⇒ Array<String>

Transform crossref relation type to relaton relation type.

Parameters:

  • crtype (String)

    The crossref relation type.

Returns:

  • (Array<String>)

    The relaton relation type and description.



706
707
708
709
710
711
712
# File 'lib/relaton/doi/parser.rb', line 706

def relation_type(crtype)
  type = REALATION_TYPES[crtype] || begin
    desc = Bib::LocalizedMarkedUpString.new(content: crtype)
    "related"
  end
  [type, desc]
end

#str_cleanup(str) ⇒ String

Clean up trailing punctuation and whitespace from a string.

Parameters:

  • str (String)

    The string to clean up.

Returns:

  • (String)

    The cleaned up string.



438
439
440
# File 'lib/relaton/doi/parser.rb', line 438

def str_cleanup(str)
  str.strip.sub(/[,\/\s]+$/, "").sub(/\s:$/, "")
end

#titlecase(str) ⇒ <Type>

Capitalize the first letter of each word in a string except for words that

are 2 letters or less.

Parameters:

  • str (<Type>)

    <description>

Returns:

  • (<Type>)

    <description>



563
564
565
566
567
568
569
570
571
# File 'lib/relaton/doi/parser.rb', line 563

def titlecase(str)
  str.split.map do |s|
    if s.size > 2 && s.upcase == s && !/\.&/.match?(s)
      s.capitalize
    else
      s
    end
  end.join " "
end