relaton 3.0.0.pre.alpha.4 → 3.0.0.pre.alpha.6

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/lib/relaton/3gpp/bibliography.rb +19 -6
  3. data/lib/relaton/3gpp/processor.rb +6 -0
  4. data/lib/relaton/adobe/processor.rb +9 -0
  5. data/lib/relaton/bib/converter/csl.rb +110 -0
  6. data/lib/relaton/bib/converter/ris.rb +104 -0
  7. data/lib/relaton/bib/converter/titles.rb +24 -0
  8. data/lib/relaton/bib/item_data.rb +23 -0
  9. data/lib/relaton/bib/model/item.rb +6 -6
  10. data/lib/relaton/bib/sanitizer.rb +71 -30
  11. data/lib/relaton/bib.rb +10 -0
  12. data/lib/relaton/bipm/bibliography.rb +24 -21
  13. data/lib/relaton/bipm/processor.rb +1 -0
  14. data/lib/relaton/bipm/rawdata_bipm_metrologia/affiliations.rb +6 -6
  15. data/lib/relaton/bipm/si_brochure_parser.rb +2 -2
  16. data/lib/relaton/bsi/bibliography.rb +26 -23
  17. data/lib/relaton/bsi/processor.rb +5 -0
  18. data/lib/relaton/calconnect/bibliography.rb +6 -6
  19. data/lib/relaton/calconnect/hit_collection.rb +4 -1
  20. data/lib/relaton/ccsds/bibliography.rb +16 -10
  21. data/lib/relaton/ccsds/hit_collection.rb +1 -1
  22. data/lib/relaton/ccsds/processor.rb +13 -0
  23. data/lib/relaton/cen/bibliography.rb +27 -24
  24. data/lib/relaton/cen/hit_collection.rb +1 -1
  25. data/lib/relaton/cen/processor.rb +5 -0
  26. data/lib/relaton/cen/scraper.rb +9 -9
  27. data/lib/relaton/cie/bibliography.rb +11 -9
  28. data/lib/relaton/cie/data_fetcher.rb +18 -18
  29. data/lib/relaton/cie/processor.rb +1 -0
  30. data/lib/relaton/cie/scrapper.rb +15 -8
  31. data/lib/relaton/cie.rb +1 -1
  32. data/lib/relaton/cloud.rb +127 -0
  33. data/lib/relaton/core/hit_collection.rb +6 -10
  34. data/lib/relaton/core/processor.rb +121 -0
  35. data/lib/relaton/core/request_error.rb +21 -3
  36. data/lib/relaton/db/cache.rb +474 -148
  37. data/lib/relaton/db/cache_entry.rb +42 -0
  38. data/lib/relaton/db/registry.rb +152 -0
  39. data/lib/relaton/db.rb +275 -102
  40. data/lib/relaton/doi/crossref.rb +23 -6
  41. data/lib/relaton/doi/processor.rb +1 -0
  42. data/lib/relaton/easc/processor.rb +1 -0
  43. data/lib/relaton/ecma/bibliography.rb +18 -13
  44. data/lib/relaton/ecma/data_fetcher.rb +1 -1
  45. data/lib/relaton/ecma/data_parser.rb +1 -1
  46. data/lib/relaton/ecma/edition_parser.rb +2 -2
  47. data/lib/relaton/ecma/memento_parser.rb +4 -4
  48. data/lib/relaton/ecma/standard_parser.rb +6 -6
  49. data/lib/relaton/etsi/bibliography.rb +8 -7
  50. data/lib/relaton/etsi/processor.rb +1 -0
  51. data/lib/relaton/gb/bibliography.rb +20 -12
  52. data/lib/relaton/gb/gb_scraper.rb +5 -5
  53. data/lib/relaton/gb/scraper.rb +16 -16
  54. data/lib/relaton/gb/sec_scraper.rb +8 -8
  55. data/lib/relaton/gb/t_scraper.rb +5 -5
  56. data/lib/relaton/gost/processor.rb +1 -0
  57. data/lib/relaton/iala/bibliography.rb +15 -12
  58. data/lib/relaton/iala/processor.rb +1 -0
  59. data/lib/relaton/iana/bibliography.rb +12 -12
  60. data/lib/relaton/iana/data_fetcher.rb +3 -3
  61. data/lib/relaton/iana/parser.rb +12 -5
  62. data/lib/relaton/iana/processor.rb +9 -0
  63. data/lib/relaton/iec/bibliography.rb +18 -7
  64. data/lib/relaton/iec/data_parser.rb +24 -9
  65. data/lib/relaton/iec/processor.rb +11 -0
  66. data/lib/relaton/iec.rb +1 -1
  67. data/lib/relaton/ieee/bibliography.rb +19 -13
  68. data/lib/relaton/ieee/data_fetcher.rb +1 -1
  69. data/lib/relaton/ieee/processor.rb +24 -0
  70. data/lib/relaton/ieee/rawbib_id_parser.rb +2 -2
  71. data/lib/relaton/ietf/bibliography.rb +11 -9
  72. data/lib/relaton/ietf/data_fetcher.rb +1 -1
  73. data/lib/relaton/ietf/processor.rb +1 -0
  74. data/lib/relaton/ietf/rfc/entry.rb +15 -19
  75. data/lib/relaton/ietf/scraper.rb +5 -4
  76. data/lib/relaton/iho/processor.rb +1 -0
  77. data/lib/relaton/index/file_io.rb +2 -2
  78. data/lib/relaton/index/pool.rb +4 -3
  79. data/lib/relaton/index/shard_source.rb +1 -1
  80. data/lib/relaton/index/type.rb +2 -5
  81. data/lib/relaton/isbn/open_library.rb +11 -7
  82. data/lib/relaton/isbn/processor.rb +10 -0
  83. data/lib/relaton/iso/bibliography.rb +11 -9
  84. data/lib/relaton/iso/data_parser.rb +2 -2
  85. data/lib/relaton/iso/processor.rb +5 -0
  86. data/lib/relaton/iso/scraper.rb +20 -20
  87. data/lib/relaton/itu/bibliography.rb +107 -16
  88. data/lib/relaton/itu/data_crawler_r.rb +2 -2
  89. data/lib/relaton/itu/hit_collection.rb +64 -52
  90. data/lib/relaton/itu/processor.rb +1 -0
  91. data/lib/relaton/itu/scraper.rb +13 -3
  92. data/lib/relaton/itu.rb +0 -2
  93. data/lib/relaton/jis/bibliography.rb +22 -11
  94. data/lib/relaton/jis/data_fetcher.rb +4 -4
  95. data/lib/relaton/jis/processor.rb +1 -0
  96. data/lib/relaton/jis/scraper.rb +8 -8
  97. data/lib/relaton/nist/bibliography.rb +25 -21
  98. data/lib/relaton/oasis/bibliography.rb +16 -13
  99. data/lib/relaton/oasis/browser_agent.rb +2 -2
  100. data/lib/relaton/oasis/data_parser.rb +5 -5
  101. data/lib/relaton/oasis/data_parser_utils.rb +2 -2
  102. data/lib/relaton/oasis/data_part_parser.rb +7 -7
  103. data/lib/relaton/ogc/bibliography.rb +15 -14
  104. data/lib/relaton/ogc/hit_collection.rb +9 -7
  105. data/lib/relaton/ogc/processor.rb +13 -0
  106. data/lib/relaton/oiml/processor.rb +1 -0
  107. data/lib/relaton/omg/bibliography.rb +9 -9
  108. data/lib/relaton/omg/scraper.rb +14 -13
  109. data/lib/relaton/omg.rb +1 -1
  110. data/lib/relaton/plateau/bibliography.rb +9 -7
  111. data/lib/relaton/plateau/hit_collection.rb +2 -1
  112. data/lib/relaton/plateau/processor.rb +1 -0
  113. data/lib/relaton/un/bibliography.rb +21 -10
  114. data/lib/relaton/un/processor.rb +1 -0
  115. data/lib/relaton/version.rb +1 -1
  116. data/lib/relaton/w3c/processor.rb +1 -0
  117. data/lib/relaton/xsf/bibliography.rb +11 -6
  118. data/lib/relaton.rb +13 -0
  119. metadata +60 -14
  120. data/lib/relaton/itu/pubid.rb +0 -199
@@ -8,23 +8,27 @@ module Relaton
8
8
 
9
9
  ENDPOINT = "http://openlibrary.org/api/volumes/brief/isbn/".freeze
10
10
 
11
- def get(ref, _date = nil, _opts = {}) # rubocop:disable Metrics/MethodLength
12
- Util.info "Fetching from OpenLibrary ...", key: ref
13
-
14
- isbn = Isbn.new(ref).parse
11
+ # @param ref [String, Pubid::Isbn::Identifier] an ISBN-10 or ISBN-13
12
+ # @return [Relaton::Bib::ItemData, nil]
13
+ def get(ref, _date = nil, _opts = {}) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
14
+ Util.info "Fetching from OpenLibrary ...", key: ref.to_s
15
+
16
+ # A parsed pubid gives its digits (`raw`); `Isbn#parse` validates them
17
+ # and converts an ISBN-10 to ISBN-13, as for a String.
18
+ isbn = Isbn.new(ref.is_a?(String) ? ref : ref.raw).parse
15
19
  unless isbn
16
- Util.info "Incorrect ISBN.", key: ref
20
+ Util.info "Incorrect ISBN.", key: ref.to_s
17
21
  return
18
22
  end
19
23
 
20
24
  resp = request_api isbn
21
25
  unless resp
22
- Util.info "Not found.", key: ref
26
+ Util.info "Not found.", key: ref.to_s
23
27
  return
24
28
  end
25
29
 
26
30
  bib = Parser.parse resp
27
- Util.info "Found: `#{bib.docidentifier.first.content}`", key: ref
31
+ Util.info "Found: `#{bib.docidentifier.first.content}`", key: ref.to_s
28
32
  bib
29
33
  end
30
34
 
@@ -8,6 +8,7 @@ module Relaton
8
8
  def initialize # rubocop:disable Lint/MissingSuper
9
9
  @short = :relaton_isbn
10
10
  @prefix = "ISBN"
11
+ @pubid_identifier = :Isbn # Db cache key
11
12
  @defaultprefix = /^ISBN\s/
12
13
  @idtype = "ISBN"
13
14
  @datasets = %w[]
@@ -22,6 +23,15 @@ module Relaton
22
23
  ::Relaton::Isbn::OpenLibrary.get(code, date, opts)
23
24
  end
24
25
 
26
+ # `OpenLibrary.get` reads an incorrect ISBN as a miss, so a reference
27
+ # pubid cannot parse (it checks the check digit too) gets no key here
28
+ # either: it is not cached.
29
+ def cache_pubid(ref)
30
+ super
31
+ rescue StandardError
32
+ nil
33
+ end
34
+
25
35
  #
26
36
  # @param xml [String]
27
37
  # @return [Relaton::Bib::Bibitem]
@@ -24,7 +24,9 @@ module Relaton
24
24
  raise Relaton::RequestError, e.message
25
25
  end
26
26
 
27
- # @param ref [String] the ISO standard Code to look up (e..g "ISO 9000")
27
+ # @param ref [String, Pubid::Iso::Identifier] the ISO standard Code to
28
+ # look up (e..g "ISO 9000"), or the parse that Relaton::Db routed with
29
+ # (relaton#205); a pubid is never mutated
28
30
  # @param year [String, NilClass] the year the standard was published
29
31
  # @param opts [Hash] options; restricted to :all_parts if all-parts
30
32
  # @option opts [Boolean] :all_parts if all-parts reference is required
@@ -33,21 +35,21 @@ module Relaton
33
35
  #
34
36
  # @return [RelatonIsoBib::IsoBibliographicItem] Bibliographic item
35
37
  def get(ref, year = nil, opts = {}) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity,Metrics/AbcSize
36
- code = ref.gsub("\u2013", "-")
37
-
38
- # parse "all parts" request
39
- # code.sub! " (all parts)", ""
40
- # opts[:all_parts] ||= $~ && opts[:all_parts].nil?
41
-
42
- query_pubid = ::Pubid::Iso::Identifier.parse(code)
38
+ query_pubid = if ref.is_a?(String)
39
+ ::Pubid::Iso::Identifier.parse(ref.gsub("\u2013", "-"))
40
+ else
41
+ ref
42
+ end
43
43
  if year&.respond_to?(:to_i)
44
+ # Copy first: a caller's pubid is also its Db cache key.
45
+ query_pubid = query_pubid.class.from_hash(query_pubid.to_hash)
44
46
  query_pubid.root.date = ::Pubid::Components::Date.new(year: year.to_s)
45
47
  end
46
48
  query_pubid = query_pubid.to_all_parts if opts[:all_parts]
47
49
  Util.info "Fetching from Relaton repository ...", key: query_pubid.to_s
48
50
 
49
51
  hits, missed_year_ids = isobib_search_filter(query_pubid, opts)
50
- tip_ids = look_up_with_any_types_stages(hits, ref, opts)
52
+ tip_ids = look_up_with_any_types_stages(hits, ref.to_s, opts)
51
53
 
52
54
  date_filter = opts[:publication_date_before] || opts[:publication_date_after]
53
55
  if date_filter && !query_pubid.all_parts
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "nokogiri"
3
+ require "moxml"
4
4
  require_relative "../iso"
5
5
  require_relative "scraper"
6
6
 
@@ -331,7 +331,7 @@ module Relaton
331
331
  end
332
332
 
333
333
  def strip_html(html)
334
- Nokogiri::HTML.fragment(html).text.strip.gsub(/\s+/, " ")
334
+ Moxml.new.parse_fragment(html).map(&:text).join.strip.gsub(/\s+/, " ")
335
335
  end
336
336
 
337
337
  # ---- copyright -------------------------------------------------------
@@ -39,6 +39,11 @@ module Relaton
39
39
  DataFetcher.fetch(source, **opts)
40
40
  end
41
41
 
42
+ # `Bibliography.get` applies a year to the document the reference names.
43
+ def fold_year(pubid, year)
44
+ fold_year_on_root pubid, year
45
+ end
46
+
42
47
  # @param xml [String]
43
48
  # @return [RelatonIsoBib::IsoBibliographicItem]
44
49
  def from_xml(xml)
@@ -95,7 +95,7 @@ module Relaton
95
95
  def id
96
96
  return @id if defined?(@id)
97
97
 
98
- did = @doc.at("//h1/span[1]")
98
+ did = @doc.at_xpath("//h1/span[1]")
99
99
  @errors[:id] &&= did.nil?
100
100
  @id = did && did.text.split(" | ").first.strip
101
101
  end
@@ -117,7 +117,7 @@ module Relaton
117
117
  def edition
118
118
  return @edition if defined?(@edition)
119
119
 
120
- ed = @doc.at("//div[div[.='Edition']]/text()[last()]")
120
+ ed = @doc.at_xpath("//div[div[.='Edition']]/text()[last()]")
121
121
  @errors[:edition] &&= ed.nil?
122
122
  @edition = ed && Bib::Edition.new(content: ed.text.match(/\d+$/).to_s)
123
123
  end
@@ -172,7 +172,7 @@ module Relaton
172
172
  langs = languages.each_with_object([]) do |l, s|
173
173
  # Don't need to get page for en. We already have it.
174
174
  d = l[:path] ? get_page(l[:path])[0] : @doc
175
- unless d.at("//h5[@class='help-block'][.='недоступно на русском языке']")
175
+ unless d.at_xpath("//h5[@class='help-block'][.='недоступно на русском языке']")
176
176
  s << l
177
177
  titles += fetch_title(d, l[:lang])
178
178
 
@@ -208,7 +208,7 @@ module Relaton
208
208
 
209
209
  # Get page.
210
210
  # @param path [String] page's path
211
- # @return [Array<Nokogiri::HTML::Document, String>]
211
+ # @return [Array<Moxml::Document, String>]
212
212
  def get_page(path) # rubocop:disable Metrics/MethodLength
213
213
  try = 0
214
214
  begin
@@ -276,12 +276,12 @@ module Relaton
276
276
  # @param [Net::HTTPOK] resp HTTP response
277
277
  # @param [URI::HTTPS] uri URI of the page
278
278
  #
279
- # @return [Nokogiri::HTML4::Document] document
279
+ # @return [Moxml::Document] document
280
280
  # @raise [Relaton::RequestError] if the page could not be parsed
281
281
  #
282
282
  def try_if_fail(resp, uri)
283
283
  10.times do
284
- doc = Nokogiri::HTML(resp.body)
284
+ doc = Moxml.new.parse_html(resp.body)
285
285
  # stop trying if page has a document id
286
286
  return doc if item_ref(doc)
287
287
 
@@ -312,12 +312,12 @@ module Relaton
312
312
  #
313
313
  # Parse ID from the document.
314
314
  #
315
- # @param [Nokogiri::HTML::Document] doc document to parse
315
+ # @param [Moxml::Document] doc document to parse
316
316
  #
317
317
  # @return [String, nil] ID
318
318
  #
319
319
  def item_ref(doc)
320
- ref = doc.at("//main//section/div/div/div//h1/span[1]")
320
+ ref = doc.at_xpath("//main//section/div/div/div//h1/span[1]")
321
321
  @errors[:reference] &&= ref.nil?
322
322
  ref&.text&.strip
323
323
  end
@@ -334,7 +334,7 @@ module Relaton
334
334
  def stage_code
335
335
  return @stage_code if defined?(@stage_code)
336
336
 
337
- stc = @doc.at("//ul[@class='dropdown-menu']/li[@class='active']/a/span[@class='stage-code']")
337
+ stc = @doc.at_xpath("//ul[@class='dropdown-menu']/li[@class='active']/a/span[@class='stage-code']")
338
338
  @errors[:stage] &&= stc.nil?
339
339
  @stage_code = stc&.text
340
340
  end
@@ -351,7 +351,7 @@ module Relaton
351
351
  rels = @doc.xpath(
352
352
  "//ul[@class='steps']/li", "//div[contains(@class, 'sub-step')]"
353
353
  ).reduce([]) do |a, r|
354
- type, date = relation_type(r.at("h4", "h5").text.strip)
354
+ type, date = relation_type(r.at_xpath("h4|h5").text.strip)
355
355
  next a if types.include?(type)
356
356
 
357
357
  a + create_relations(r, type, date)
@@ -383,7 +383,7 @@ module Relaton
383
383
  #
384
384
  # Create relations.
385
385
  #
386
- # @param [Nokogiri::HTML::Element] rel relation element
386
+ # @param [Moxml::Element] rel relation element
387
387
  # @param [String] type relation type
388
388
  # @param [Hash{Symbol=>String}] date relation document date
389
389
  # @option date [String] :type date type
@@ -400,7 +400,7 @@ module Relaton
400
400
  end
401
401
 
402
402
  # Fetch titles.
403
- # @param doc [Nokogiri::HTML::Document]
403
+ # @param doc [Moxml::Document]
404
404
  # @param lang [String]
405
405
  # @return [Array<RelatonBib::TypedTitleString>]
406
406
  def fetch_title(doc, lang) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
@@ -417,7 +417,7 @@ module Relaton
417
417
  end
418
418
 
419
419
  def parse_titles(doc)
420
- # head = doc.at "//nav[contains(@class,'heading-condensed')]"
420
+ # head = doc.at_xpath "//nav[contains(@class,'heading-condensed')]"
421
421
  ttls = doc.xpath("//h1[@class='stdTitle']/span[position()>1]").map(&:text)
422
422
  return ttls if @errors[:title] &&= ttls.empty?
423
423
 
@@ -455,14 +455,14 @@ module Relaton
455
455
  def fetch_dates # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
456
456
  dates = []
457
457
  %r{^[^\s]+\s[\d-]+:(?<ref_date_str>\d{4})} =~ id
458
- pub_date_str = @doc.at("//span[@itemprop='releaseDate']")
458
+ pub_date_str = @doc.at_xpath("//span[@itemprop='releaseDate']")
459
459
  @errors[:date_pub] &&= pub_date_str.nil?
460
460
  if ref_date_str
461
461
  dates += parse_date_from_id ref_date_str, pub_date_str
462
462
  elsif pub_date_str
463
463
  dates << Bib::Date.new(type: "published", at: pub_date_str.text)
464
464
  end
465
- corr_data = @doc.at "//span[@itemprop='dateModified']"
465
+ corr_data = @doc.at_xpath "//span[@itemprop='dateModified']"
466
466
  @errors[:date_corr] &&= corr_data.nil?
467
467
  dates << Bib::Date.new(type: "corrected", at: corr_data.text) if corr_data
468
468
  dates
@@ -522,13 +522,13 @@ module Relaton
522
522
  #
523
523
  def fetch_source(url) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength
524
524
  source = [Bib::Uri.new(type: "src", content: url)]
525
- obp = @doc.at("//a[.='Read sample']")
525
+ obp = @doc.at_xpath("//a[.='Read sample']")
526
526
  @errors[:link_obp] &&= obp.nil?
527
527
  source << Bib::Uri.new(type: "obp", content: obp[:href]) if obp
528
- rss = @doc.at("//a[contains(@href, 'rss')]")
528
+ rss = @doc.at_xpath("//a[contains(@href, 'rss')]")
529
529
  @errors[:link_rss] &&= rss.nil?
530
530
  source << Bib::Uri.new(type: "rss", content: DOMAIN + rss[:href]) if rss
531
- pub = @doc.at "//p[contains(., 'publicly available')]/a",
531
+ pub = @doc.at_xpath "//p[contains(., 'publicly available')]/a",
532
532
  "//p[contains(., 'can be downloaded from the')]/a"
533
533
  @errors[:link_pub] &&= pub.nil?
534
534
  source << Bib::Uri.new(type: "pub", content: pub[:href]) if pub
@@ -542,7 +542,7 @@ module Relaton
542
542
  owner_name = ref.match(/.*?(?=\s)/).to_s
543
543
  from = ref.match(/(?<=:)\d{4}/).to_s
544
544
  if from.empty?
545
- date = @doc.at(
545
+ date = @doc.at_xpath(
546
546
  "//span[@itemprop='releaseDate']",
547
547
  "//ul[@id='stages']/li[contains(@class,'active')]/ul/li[@class='active']/a/span[@class='stage-date']",
548
548
  )
@@ -585,7 +585,7 @@ module Relaton
585
585
  # @return [Relaton::Bib::Contributor, nil]
586
586
  #
587
587
  def fetch_editorialgroup_contributor # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
588
- wg = @doc.at(
588
+ wg = @doc.at_xpath(
589
589
  "//div[contains(., 'Technical Committe')]" \
590
590
  "/following-sibling::span/a",
591
591
  )
@@ -5,35 +5,97 @@ module Relaton
5
5
  module Bibliography
6
6
  extend self
7
7
 
8
- # @param refid [Relaton::Itu::Pubid, String] a document reference
8
+ # Supplements that print the date of the recommendation they amend
9
+ # first, "ITU-T G.989.2 (2014) Amd. 1 (04/2016)": their edition is the
10
+ # base's.
11
+ AMENDING = [
12
+ ::Pubid::Itu::Identifiers::Amendment, ::Pubid::Itu::Identifiers::Corrigendum,
13
+ ::Pubid::Itu::Identifiers::Errata, ::Pubid::Itu::Identifiers::Addendum,
14
+ ].freeze
15
+
16
+ # Parts of a recommendation that print its date first when it has one,
17
+ # "ITU-T Z.100 (06/2021) Annex F1", and their own date otherwise.
18
+ ANNEXED = [
19
+ ::Pubid::Itu::Identifiers::AnnexOfRecommendation,
20
+ ::Pubid::Itu::Identifiers::AppendixOfRecommendation,
21
+ ].freeze
22
+
23
+ # @param refid [Pubid::Itu::Identifier, String] a document reference; a
24
+ # String that Pubid::Itu cannot parse raises Pubid::Errors::ParseError
9
25
  # @return [Relaton::Itu::HitCollection]
10
26
  def search(refid)
11
27
  if refid.is_a? String
12
28
  warn_incorrect_ref(refid)
13
- refid = Pubid.parse refid
29
+ refid = ::Pubid::Itu.parse refid
14
30
  end
15
31
  HitCollection.new(refid).tap(&:search)
16
32
  end
17
33
 
18
- # @param code [String] the ITU standard Code to look up
34
+ # @param ref [String, Pubid::Itu::Identifier] the ITU standard Code to
35
+ # look up, or its parse from Relaton::Db (relaton#205); #with_year
36
+ # copies, so the pubid is not changed
19
37
  # @param year [String] the year the standard was published (optional)
20
38
  # @param opts [Hash] options
21
39
  # @return [Relaton::Bib::ItemData, nil]
22
- def get(code, year = nil, opts = {})
23
- warn_incorrect_ref(code)
24
- refid = Pubid.parse code
25
- refid.year ||= year
40
+ def get(ref, year = nil, opts = {})
41
+ if ref.is_a? String
42
+ warn_incorrect_ref(ref)
43
+ ref = ::Pubid::Itu.parse(ref)
44
+ end
45
+ refid = with_year ref, year
26
46
 
27
47
  ret = itubib_get1(refid)
28
48
  return nil if ret.nil?
29
49
 
30
- ret = ret.to_most_recent_reference unless refid.year || opts[:keep_year]
50
+ unless edition_year(refid) || opts[:keep_year]
51
+ ret = ret.to_most_recent_reference
52
+ end
31
53
  ret = ret.to_all_parts if opts[:all_parts]
32
54
  ret
33
55
  end
34
56
 
57
+ # The year a reference selects its hits by. It is read the way a hit
58
+ # code's first date is: for an amending supplement, it is the year of
59
+ # the recommendation it amends. HitCollection uses it too.
60
+ #
61
+ # @param refid [Pubid::Itu::Identifier]
62
+ # @return [String, nil]
63
+ def edition_year(refid)
64
+ return refid.base&.year if amending?(refid)
65
+ return refid.base&.year || refid.year if annexed?(refid)
66
+
67
+ refid.year
68
+ end
69
+
70
+ # The identifier a reference is a part of: the recommendation an
71
+ # amending supplement, an annex or an appendix belongs to; else itself.
72
+ # A series or recommendation supplement is a document of its own.
73
+ #
74
+ # @param refid [Pubid::Itu::Identifier]
75
+ # @return [Pubid::Itu::Identifier]
76
+ def document(refid)
77
+ amending?(refid) || annexed?(refid) ? refid.base : refid
78
+ end
79
+
35
80
  private
36
81
 
82
+ # @param refid [Pubid::Itu::Identifier]
83
+ # @param year [String, nil] the year passed beside the reference
84
+ # @return [Pubid::Itu::Identifier] refid, dated with year when it has
85
+ # no year of its own
86
+ def with_year(refid, year)
87
+ return refid if year.nil? || edition_year(refid)
88
+
89
+ hash = refid.to_hash
90
+ dated = amending?(refid) ? hash["base"] : hash
91
+ dated["year"] = year.to_s
92
+ ::Pubid::Itu::Identifier.from_hash hash
93
+ end
94
+
95
+ def amending?(refid) = AMENDING.any? { |klass| refid.is_a? klass }
96
+
97
+ def annexed?(refid) = ANNEXED.any? { |klass| refid.is_a? klass }
98
+
37
99
  def warn_incorrect_ref(ref)
38
100
  if ref =~ /(ITU[\s-]T\s\w)\.(Suppl\.|Annex)\s?(\w?\d+)/
39
101
  correct_ref = "#{$~[1]} #{$~[2]} #{$~[3]}"
@@ -45,30 +107,59 @@ module Relaton
45
107
  Util.info "Not found.", key: refid.to_s
46
108
  # one hit per edition means a year can repeat (a recommendation and its
47
109
  # amendment); the requested year itself is not a "different year" hint
48
- years = missed_years.uniq.reject { |y| y == refid.year }
110
+ year = edition_year(refid)
111
+ years = missed_years.uniq.reject { |y| y == year }
49
112
  if years.any?
50
113
  plural = years.size > 1 ? "s" : ""
51
- Util.info "There was no match for `#{refid.year}` year, though there were matches " \
52
- "found for `#{years.join('`, `')}` year#{plural}.", key: refid.to_s
114
+ Util.info "There was no match for `#{year}` year, though there " \
115
+ "were matches found for `#{years.join('`, `')}` " \
116
+ "year#{plural}.", key: refid.to_s
53
117
  end
54
118
  nil
55
119
  end
56
120
 
121
+ # Keep the hits that are the referenced document, in any edition:
122
+ # #isobib_results_filter picks the year afterwards. The date is ignored,
123
+ # and the version only when the reference names none. Everything else
124
+ # must be equal, so a supplement, or a Report for a Recommendation, never
125
+ # matches.
126
+ #
127
+ # Not `===`: it reads a missing `subseries` as "any value", so
128
+ # `ITU-T H.264` would keep `ITU-T H.264.1`.
57
129
  def search_filter(refid)
58
- result = search(refid)
59
- result.select do |i|
130
+ ignore = %i[year month day]
131
+ ignore << :version unless stated_version?(refid)
132
+ search(refid).select do |i|
60
133
  next true unless i.hit[:code]
61
134
 
62
- pubid = Pubid.parse i.hit[:code]
63
- refid.===(pubid, [:year])
135
+ hit = ::Pubid::Itu.parse(i.hit[:code])
136
+ refid.matches?(hit, ignore: ignore) && same_own_date?(refid, hit)
137
+ rescue ::Pubid::Errors::ParseError
138
+ false # a live hit ITU spells in a form nothing can identify
64
139
  end
65
140
  end
66
141
 
142
+ # A version on the reference, or on the recommendation it amends.
143
+ def stated_version?(refid)
144
+ id = document(refid)
145
+ id.respond_to?(:version) && !id.version.nil?
146
+ end
147
+
148
+ # `ignore:` drops the date at every level, but an amending supplement's
149
+ # own date tells two "Amd. 1" to different editions apart.
150
+ def same_own_date?(refid, hit)
151
+ return true unless amending?(refid)
152
+
153
+ own = refid.to_hash.slice("year", "month", "day")
154
+ own.all? { |key, value| hit.to_hash[key] == value }
155
+ end
156
+
67
157
  def isobib_results_filter(result, refid)
68
158
  missed_years = []
159
+ year = edition_year(refid)
69
160
  result.each do |r|
70
161
  /\((?:\d{2}\/)?(?<pyear>\d{4})\)/ =~ r.hit[:code]
71
- if !refid.year || refid.year == pyear
162
+ if !year || year == pyear
72
163
  ret = r.item
73
164
  return { ret: ret } if ret
74
165
  end
@@ -564,7 +564,7 @@ module Relaton
564
564
  # the first cell, the title in the second.
565
565
  #
566
566
  # @param url [String]
567
- # @return [Array<Array(String, Nokogiri::XML::Element, Nokogiri::XML::NodeSet)>]
567
+ # @return [Array<Array(String, Moxml::Element, Moxml::NodeSet)>]
568
568
  def rows(url)
569
569
  get(url).search("//a[contains(@href,'parent=R-')]").filter_map do |a|
570
570
  id = a[:href].to_s[PARENT_RE, :id]
@@ -576,7 +576,7 @@ module Relaton
576
576
  # The title cell carries an optional red annotation ("Note - Suppressed on
577
577
  # …"), which is not part of the title.
578
578
  #
579
- # @param cell [Nokogiri::XML::Element, nil]
579
+ # @param cell [Moxml::Element, nil]
580
580
  # @return [String]
581
581
  def title_text(cell)
582
582
  return "" unless cell