relaton 3.0.0.pre.alpha.4 → 3.0.0.pre.alpha.5
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/lib/relaton/adobe/processor.rb +9 -0
- data/lib/relaton/bib/converter/csl.rb +110 -0
- data/lib/relaton/bib/converter/ris.rb +104 -0
- data/lib/relaton/bib/converter/titles.rb +24 -0
- data/lib/relaton/bib/item_data.rb +9 -0
- data/lib/relaton/bib/model/item.rb +6 -6
- data/lib/relaton/bib/sanitizer.rb +71 -30
- data/lib/relaton/bib.rb +10 -0
- data/lib/relaton/bipm/processor.rb +1 -0
- data/lib/relaton/bipm/rawdata_bipm_metrologia/affiliations.rb +6 -6
- data/lib/relaton/bipm/si_brochure_parser.rb +2 -2
- data/lib/relaton/bsi/processor.rb +5 -0
- data/lib/relaton/ccsds/bibliography.rb +1 -0
- data/lib/relaton/ccsds/processor.rb +12 -0
- data/lib/relaton/cen/hit_collection.rb +1 -1
- data/lib/relaton/cen/processor.rb +5 -0
- data/lib/relaton/cen/scraper.rb +9 -9
- data/lib/relaton/cie/data_fetcher.rb +18 -18
- data/lib/relaton/cie/processor.rb +1 -0
- data/lib/relaton/cie.rb +1 -1
- data/lib/relaton/cloud.rb +127 -0
- data/lib/relaton/core/hit_collection.rb +6 -10
- data/lib/relaton/core/processor.rb +68 -0
- data/lib/relaton/db/cache.rb +444 -148
- data/lib/relaton/db/cache_entry.rb +33 -0
- data/lib/relaton/db/registry.rb +52 -0
- data/lib/relaton/db.rb +131 -72
- data/lib/relaton/doi/crossref.rb +23 -6
- data/lib/relaton/doi/processor.rb +1 -0
- data/lib/relaton/easc/processor.rb +1 -0
- data/lib/relaton/ecma/data_fetcher.rb +1 -1
- data/lib/relaton/ecma/data_parser.rb +1 -1
- data/lib/relaton/ecma/edition_parser.rb +2 -2
- data/lib/relaton/ecma/memento_parser.rb +4 -4
- data/lib/relaton/ecma/standard_parser.rb +6 -6
- data/lib/relaton/etsi/processor.rb +1 -0
- data/lib/relaton/gb/gb_scraper.rb +5 -5
- data/lib/relaton/gb/scraper.rb +16 -16
- data/lib/relaton/gb/sec_scraper.rb +8 -8
- data/lib/relaton/gb/t_scraper.rb +5 -5
- data/lib/relaton/gost/processor.rb +1 -0
- data/lib/relaton/iala/processor.rb +1 -0
- data/lib/relaton/iana/data_fetcher.rb +3 -3
- data/lib/relaton/iana/parser.rb +8 -4
- data/lib/relaton/iana/processor.rb +9 -0
- data/lib/relaton/iec/data_parser.rb +24 -9
- data/lib/relaton/iec/processor.rb +6 -0
- data/lib/relaton/iec.rb +1 -1
- data/lib/relaton/ieee/data_fetcher.rb +1 -1
- data/lib/relaton/ieee/processor.rb +8 -0
- data/lib/relaton/ietf/data_fetcher.rb +1 -1
- data/lib/relaton/ietf/processor.rb +1 -0
- data/lib/relaton/ietf/rfc/entry.rb +15 -19
- data/lib/relaton/iho/processor.rb +1 -0
- data/lib/relaton/index/file_io.rb +2 -2
- data/lib/relaton/index/pool.rb +4 -3
- data/lib/relaton/index/shard_source.rb +1 -1
- data/lib/relaton/index/type.rb +2 -5
- data/lib/relaton/isbn/open_library.rb +11 -7
- data/lib/relaton/isbn/processor.rb +10 -0
- data/lib/relaton/iso/data_parser.rb +2 -2
- data/lib/relaton/iso/processor.rb +5 -0
- data/lib/relaton/iso/scraper.rb +20 -20
- data/lib/relaton/itu/bibliography.rb +99 -13
- data/lib/relaton/itu/data_crawler_r.rb +2 -2
- data/lib/relaton/itu/hit_collection.rb +64 -52
- data/lib/relaton/itu/processor.rb +1 -0
- data/lib/relaton/itu/scraper.rb +13 -3
- data/lib/relaton/itu.rb +0 -2
- data/lib/relaton/jis/data_fetcher.rb +4 -4
- data/lib/relaton/jis/processor.rb +1 -0
- data/lib/relaton/jis/scraper.rb +8 -8
- data/lib/relaton/oasis/browser_agent.rb +2 -2
- data/lib/relaton/oasis/data_parser.rb +5 -5
- data/lib/relaton/oasis/data_parser_utils.rb +2 -2
- data/lib/relaton/oasis/data_part_parser.rb +7 -7
- data/lib/relaton/ogc/processor.rb +7 -0
- data/lib/relaton/oiml/processor.rb +1 -0
- data/lib/relaton/omg/scraper.rb +11 -11
- data/lib/relaton/omg.rb +1 -1
- data/lib/relaton/plateau/processor.rb +1 -0
- data/lib/relaton/un/bibliography.rb +21 -10
- data/lib/relaton/un/processor.rb +1 -0
- data/lib/relaton/version.rb +1 -1
- data/lib/relaton/w3c/processor.rb +1 -0
- data/lib/relaton.rb +13 -0
- metadata +48 -16
- data/lib/relaton/itu/pubid.rb +0 -199
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
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
|
-
|
|
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)
|
data/lib/relaton/iso/scraper.rb
CHANGED
|
@@ -95,7 +95,7 @@ module Relaton
|
|
|
95
95
|
def id
|
|
96
96
|
return @id if defined?(@id)
|
|
97
97
|
|
|
98
|
-
did = @doc.
|
|
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.
|
|
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.
|
|
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<
|
|
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 [
|
|
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 =
|
|
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 [
|
|
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.
|
|
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.
|
|
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.
|
|
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 [
|
|
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 [
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
588
|
+
wg = @doc.at_xpath(
|
|
589
589
|
"//div[contains(., 'Technical Committe')]" \
|
|
590
590
|
"/following-sibling::span/a",
|
|
591
591
|
)
|
|
@@ -5,12 +5,28 @@ module Relaton
|
|
|
5
5
|
module Bibliography
|
|
6
6
|
extend self
|
|
7
7
|
|
|
8
|
-
#
|
|
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
|
|
@@ -21,19 +37,60 @@ module Relaton
|
|
|
21
37
|
# @return [Relaton::Bib::ItemData, nil]
|
|
22
38
|
def get(code, year = nil, opts = {})
|
|
23
39
|
warn_incorrect_ref(code)
|
|
24
|
-
refid = Pubid.parse
|
|
25
|
-
refid.year ||= year
|
|
40
|
+
refid = with_year ::Pubid::Itu.parse(code), year
|
|
26
41
|
|
|
27
42
|
ret = itubib_get1(refid)
|
|
28
43
|
return nil if ret.nil?
|
|
29
44
|
|
|
30
|
-
|
|
45
|
+
unless edition_year(refid) || opts[:keep_year]
|
|
46
|
+
ret = ret.to_most_recent_reference
|
|
47
|
+
end
|
|
31
48
|
ret = ret.to_all_parts if opts[:all_parts]
|
|
32
49
|
ret
|
|
33
50
|
end
|
|
34
51
|
|
|
52
|
+
# The year a reference selects its hits by. It is read the way a hit
|
|
53
|
+
# code's first date is: for an amending supplement, it is the year of
|
|
54
|
+
# the recommendation it amends. HitCollection uses it too.
|
|
55
|
+
#
|
|
56
|
+
# @param refid [Pubid::Itu::Identifier]
|
|
57
|
+
# @return [String, nil]
|
|
58
|
+
def edition_year(refid)
|
|
59
|
+
return refid.base&.year if amending?(refid)
|
|
60
|
+
return refid.base&.year || refid.year if annexed?(refid)
|
|
61
|
+
|
|
62
|
+
refid.year
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The identifier a reference is a part of: the recommendation an
|
|
66
|
+
# amending supplement, an annex or an appendix belongs to; else itself.
|
|
67
|
+
# A series or recommendation supplement is a document of its own.
|
|
68
|
+
#
|
|
69
|
+
# @param refid [Pubid::Itu::Identifier]
|
|
70
|
+
# @return [Pubid::Itu::Identifier]
|
|
71
|
+
def document(refid)
|
|
72
|
+
amending?(refid) || annexed?(refid) ? refid.base : refid
|
|
73
|
+
end
|
|
74
|
+
|
|
35
75
|
private
|
|
36
76
|
|
|
77
|
+
# @param refid [Pubid::Itu::Identifier]
|
|
78
|
+
# @param year [String, nil] the year passed beside the reference
|
|
79
|
+
# @return [Pubid::Itu::Identifier] refid, dated with year when it has
|
|
80
|
+
# no year of its own
|
|
81
|
+
def with_year(refid, year)
|
|
82
|
+
return refid if year.nil? || edition_year(refid)
|
|
83
|
+
|
|
84
|
+
hash = refid.to_hash
|
|
85
|
+
dated = amending?(refid) ? hash["base"] : hash
|
|
86
|
+
dated["year"] = year.to_s
|
|
87
|
+
::Pubid::Itu::Identifier.from_hash hash
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def amending?(refid) = AMENDING.any? { |klass| refid.is_a? klass }
|
|
91
|
+
|
|
92
|
+
def annexed?(refid) = ANNEXED.any? { |klass| refid.is_a? klass }
|
|
93
|
+
|
|
37
94
|
def warn_incorrect_ref(ref)
|
|
38
95
|
if ref =~ /(ITU[\s-]T\s\w)\.(Suppl\.|Annex)\s?(\w?\d+)/
|
|
39
96
|
correct_ref = "#{$~[1]} #{$~[2]} #{$~[3]}"
|
|
@@ -45,30 +102,59 @@ module Relaton
|
|
|
45
102
|
Util.info "Not found.", key: refid.to_s
|
|
46
103
|
# one hit per edition means a year can repeat (a recommendation and its
|
|
47
104
|
# amendment); the requested year itself is not a "different year" hint
|
|
48
|
-
|
|
105
|
+
year = edition_year(refid)
|
|
106
|
+
years = missed_years.uniq.reject { |y| y == year }
|
|
49
107
|
if years.any?
|
|
50
108
|
plural = years.size > 1 ? "s" : ""
|
|
51
|
-
Util.info "There was no match for `#{
|
|
52
|
-
"found for `#{years.join('`, `')}`
|
|
109
|
+
Util.info "There was no match for `#{year}` year, though there " \
|
|
110
|
+
"were matches found for `#{years.join('`, `')}` " \
|
|
111
|
+
"year#{plural}.", key: refid.to_s
|
|
53
112
|
end
|
|
54
113
|
nil
|
|
55
114
|
end
|
|
56
115
|
|
|
116
|
+
# Keep the hits that are the referenced document, in any edition:
|
|
117
|
+
# #isobib_results_filter picks the year afterwards. The date is ignored,
|
|
118
|
+
# and the version only when the reference names none. Everything else
|
|
119
|
+
# must be equal, so a supplement, or a Report for a Recommendation, never
|
|
120
|
+
# matches.
|
|
121
|
+
#
|
|
122
|
+
# Not `===`: it reads a missing `subseries` as "any value", so
|
|
123
|
+
# `ITU-T H.264` would keep `ITU-T H.264.1`.
|
|
57
124
|
def search_filter(refid)
|
|
58
|
-
|
|
59
|
-
|
|
125
|
+
ignore = %i[year month day]
|
|
126
|
+
ignore << :version unless stated_version?(refid)
|
|
127
|
+
search(refid).select do |i|
|
|
60
128
|
next true unless i.hit[:code]
|
|
61
129
|
|
|
62
|
-
|
|
63
|
-
refid
|
|
130
|
+
hit = ::Pubid::Itu.parse(i.hit[:code])
|
|
131
|
+
refid.matches?(hit, ignore: ignore) && same_own_date?(refid, hit)
|
|
132
|
+
rescue ::Pubid::Errors::ParseError
|
|
133
|
+
false # a live hit ITU spells in a form nothing can identify
|
|
64
134
|
end
|
|
65
135
|
end
|
|
66
136
|
|
|
137
|
+
# A version on the reference, or on the recommendation it amends.
|
|
138
|
+
def stated_version?(refid)
|
|
139
|
+
id = document(refid)
|
|
140
|
+
id.respond_to?(:version) && !id.version.nil?
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# `ignore:` drops the date at every level, but an amending supplement's
|
|
144
|
+
# own date tells two "Amd. 1" to different editions apart.
|
|
145
|
+
def same_own_date?(refid, hit)
|
|
146
|
+
return true unless amending?(refid)
|
|
147
|
+
|
|
148
|
+
own = refid.to_hash.slice("year", "month", "day")
|
|
149
|
+
own.all? { |key, value| hit.to_hash[key] == value }
|
|
150
|
+
end
|
|
151
|
+
|
|
67
152
|
def isobib_results_filter(result, refid)
|
|
68
153
|
missed_years = []
|
|
154
|
+
year = edition_year(refid)
|
|
69
155
|
result.each do |r|
|
|
70
156
|
/\((?:\d{2}\/)?(?<pyear>\d{4})\)/ =~ r.hit[:code]
|
|
71
|
-
if !
|
|
157
|
+
if !year || year == pyear
|
|
72
158
|
ret = r.item
|
|
73
159
|
return { ret: ret } if ret
|
|
74
160
|
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,
|
|
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 [
|
|
579
|
+
# @param cell [Moxml::Element, nil]
|
|
580
580
|
# @return [String]
|
|
581
581
|
def title_text(cell)
|
|
582
582
|
return "" unless cell
|
|
@@ -24,21 +24,21 @@ module Relaton
|
|
|
24
24
|
RECEDITIONS_URL = "#{DOMAIN}/mws/api/recommendations/getRecEditions?idrec=%<idrec>s&lang=en".freeze
|
|
25
25
|
HANDLE_URL = "http://handle.itu.int/11.1002/1000/%<idrec>s-en".freeze
|
|
26
26
|
|
|
27
|
+
# `ref` is a Pubid::Itu identifier; its class and sector pick the route.
|
|
27
28
|
def search
|
|
28
|
-
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
when /^ITU-R\s/, /^Report\sITU-R\s/ then request_document
|
|
29
|
+
if publication?(ref) then request_publication
|
|
30
|
+
# an annex to a bulletin has no landing page, and it is not a
|
|
31
|
+
# recommendation either
|
|
32
|
+
elsif publication?(ref.root) then @array = []
|
|
33
|
+
else
|
|
34
|
+
case sector
|
|
35
|
+
when "T" then request_recommendation
|
|
36
|
+
when "R" then request_document
|
|
37
|
+
end
|
|
38
38
|
end
|
|
39
39
|
rescue Mechanize::ResponseCodeError, SocketError, Timeout::Error, Errno::ECONNRESET,
|
|
40
40
|
EOFError, Net::ProtocolError, OpenSSL::SSL::SSLError => e
|
|
41
|
-
raise Relaton::RequestError, "Could not access #{ref
|
|
41
|
+
raise Relaton::RequestError, "Could not access #{ref}: #{e.message}"
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def agent
|
|
@@ -70,6 +70,21 @@ module Relaton
|
|
|
70
70
|
|
|
71
71
|
private
|
|
72
72
|
|
|
73
|
+
# Radio Regulations and Operational Bulletins live on www.itu.int/pub,
|
|
74
|
+
# not in the dataset.
|
|
75
|
+
def publication?(id)
|
|
76
|
+
id.is_a?(::Pubid::Itu::Identifiers::RadioRegulations) ||
|
|
77
|
+
id.is_a?(::Pubid::Itu::Identifiers::SpecialPublication)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# An annex or an appendix carries its sector on the recommendation it
|
|
81
|
+
# belongs to.
|
|
82
|
+
#
|
|
83
|
+
# @return [String, nil] "T" or "R"
|
|
84
|
+
def sector
|
|
85
|
+
(ref.sector || ref.root.sector)&.to_s
|
|
86
|
+
end
|
|
87
|
+
|
|
73
88
|
# ITU-T Recommendations and supplements. The index is preferred (offline,
|
|
74
89
|
# every edition, no scraping); the live path covers what the dataset does
|
|
75
90
|
# not have — Implementers' Guides, the `(V##)`/`Annex` forms the data
|
|
@@ -86,9 +101,8 @@ module Relaton
|
|
|
86
101
|
|
|
87
102
|
# @return [Array<Relaton::Itu::Hit>] newest edition first
|
|
88
103
|
def index_hits
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
# Every edition is a candidate; Bibliography selects the year.
|
|
105
|
+
pubid = ref.exclude(:year, :month, :day)
|
|
92
106
|
Util.info "Fetching from Relaton repository ...", key: ref.to_s
|
|
93
107
|
index.search(pubid) { |i| index_match?(pubid, i[:id]) }
|
|
94
108
|
.map { |r| index_hit r }.sort_by { |h| edition_key h.hit[:code] }.reverse
|
|
@@ -112,7 +126,10 @@ module Relaton
|
|
|
112
126
|
def year_satisfied?(hits)
|
|
113
127
|
return false if hits.empty?
|
|
114
128
|
|
|
115
|
-
|
|
129
|
+
year = Bibliography.edition_year(ref)
|
|
130
|
+
return true if year.nil?
|
|
131
|
+
|
|
132
|
+
hits.any? { |h| edition_key(h.hit[:code]).first == year.to_i }
|
|
116
133
|
end
|
|
117
134
|
|
|
118
135
|
# The edition date of a rendered docidentifier, as `[year, month]`. Uses
|
|
@@ -135,14 +152,13 @@ module Relaton
|
|
|
135
152
|
|
|
136
153
|
def request_document # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
|
|
137
154
|
Util.info "Fetching from Relaton repository ...", key: ref.to_s
|
|
138
|
-
# Pass the reference
|
|
155
|
+
# Pass the reference as a pubid (not a String) so Relaton::Index can
|
|
139
156
|
# narrow candidates by document number before the block; `index_match?`
|
|
140
157
|
# then matches every edition when the reference omits the part and the
|
|
141
158
|
# exact edition when it names one. Rows are Pubid::Itu objects (not
|
|
142
159
|
# Comparable), so rank by the numeric edition in `code.parts` (`["3"]` → 3)
|
|
143
160
|
# to return the latest — index order isn't by edition.
|
|
144
|
-
|
|
145
|
-
row = index.search(pubid) { |i| index_match?(pubid, i[:id]) }
|
|
161
|
+
row = index.search(ref) { |i| index_match?(ref, i[:id]) }
|
|
146
162
|
.max_by { |i| i[:id].code&.parts&.last.to_i }
|
|
147
163
|
return unless row
|
|
148
164
|
|
|
@@ -155,30 +171,6 @@ module Relaton
|
|
|
155
171
|
@array = [hit]
|
|
156
172
|
end
|
|
157
173
|
|
|
158
|
-
# Parse the reference into a Pubid::Itu identifier for the index lookup. A
|
|
159
|
-
# ref pubid can't parse raises (a `Pubid`/`Parslet` error) and propagates to
|
|
160
|
-
# the caller (relaton-cli / API callers rescue it), mirroring the ETSI
|
|
161
|
-
# flavor — the consumer no longer degrades to a raw-string substring search.
|
|
162
|
-
#
|
|
163
|
-
# @return [::Pubid::Itu::Identifier]
|
|
164
|
-
def pubid_ref
|
|
165
|
-
::Pubid::Itu.parse ref.to_ref
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# The reference as a pubid for an ITU-T index lookup, stripped of its
|
|
169
|
-
# edition date (every edition is a candidate; Bibliography selects the
|
|
170
|
-
# year). Unlike `#pubid_ref` this **rescues** an unparseable reference: the
|
|
171
|
-
# ITU-T path has a live fallback to degrade to, and a malformed reference
|
|
172
|
-
# (e.g. `ITU-T G.Suppl.47`) should warn and report "Not found", not raise.
|
|
173
|
-
#
|
|
174
|
-
# @return [::Pubid::Itu::Identifier, nil]
|
|
175
|
-
def index_pubid_ref
|
|
176
|
-
undated = ref.dup.tap { |r| r.year = r.month = r.day = nil }
|
|
177
|
-
::Pubid::Itu.parse undated.to_ref
|
|
178
|
-
rescue StandardError
|
|
179
|
-
nil
|
|
180
|
-
end
|
|
181
|
-
|
|
182
174
|
# Does an index row's id match the reference? When the reference omits the
|
|
183
175
|
# part/edition (a bare `ITU-R P.838`), every edition of that exact document
|
|
184
176
|
# matches — "search all parts"; when it names a part (`ITU-R P.838-2`), only
|
|
@@ -227,9 +219,15 @@ module Relaton
|
|
|
227
219
|
nil
|
|
228
220
|
end
|
|
229
221
|
|
|
230
|
-
#
|
|
222
|
+
# The `rec=` value for the rec.aspx lookup: the document without its
|
|
223
|
+
# sector, edition date or version — "H.264", "A Suppl. 2",
|
|
224
|
+
# "G.989 Suppl. 1". An amendment or an annex is looked up by the
|
|
225
|
+
# recommendation it belongs to.
|
|
226
|
+
#
|
|
227
|
+
# @return [String]
|
|
231
228
|
def rec_query
|
|
232
|
-
|
|
229
|
+
Bibliography.document(ref).exclude(:year, :month, :day, :version)
|
|
230
|
+
.to_s.delete_prefix("ITU-T ")
|
|
233
231
|
end
|
|
234
232
|
|
|
235
233
|
# @param idrec [String]
|
|
@@ -244,10 +242,14 @@ module Relaton
|
|
|
244
242
|
# @param edition [Hash] a getRecEditions entry
|
|
245
243
|
# @return [Relaton::Itu::Hit]
|
|
246
244
|
def recommendation_hit(edition)
|
|
245
|
+
# The crawler's rec_name canonicalisation ("G 231" -> "G.231"): the
|
|
246
|
+
# code is parsed with Pubid::Itu, which rejects the space spelling.
|
|
247
|
+
require_relative "data_parser_t"
|
|
248
|
+
name = DataParserT.normalize_rec_name edition["rec_name"]
|
|
247
249
|
# No `:ref` key: it drives Hit#gi_imp, which would route an Implementers'
|
|
248
250
|
# Guide reference onto the getImplGuides endpoints.
|
|
249
251
|
Hit.new({
|
|
250
|
-
code: "ITU-T #{
|
|
252
|
+
code: "ITU-T #{name}",
|
|
251
253
|
title: edition["title"],
|
|
252
254
|
url: format(HANDLE_URL, idrec: edition["idrec"]),
|
|
253
255
|
type: "recommendation",
|
|
@@ -279,21 +281,31 @@ module Relaton
|
|
|
279
281
|
end
|
|
280
282
|
|
|
281
283
|
# The /pub identifier for a Radio Regulation or an Operational Bulletin
|
|
282
|
-
# (`OB.1096` → `T-SP-OB.1096-2016`). nil — no hit —
|
|
283
|
-
#
|
|
284
|
+
# (`OB.1096` → `T-SP-OB.1096-2016`). nil — no hit — for any other special
|
|
285
|
+
# publication, which has no derivable landing page.
|
|
284
286
|
#
|
|
285
287
|
# @return [String, nil]
|
|
286
288
|
def publication_id
|
|
287
|
-
return "R-REG-RR-#{ref.year}" if
|
|
289
|
+
return "R-REG-RR-#{ref.year}" if radio_regulations?
|
|
288
290
|
|
|
289
|
-
|
|
290
|
-
"T-SP-OB.#{num}-#{ref.year}" if num
|
|
291
|
+
"T-SP-OB.#{ref.number}-#{ref.year}" if bulletin?
|
|
291
292
|
end
|
|
292
293
|
|
|
293
|
-
#
|
|
294
|
+
# The docidentifier (year only, no month or day). A bulletin keeps its
|
|
295
|
+
# sector even when the reference is the sector-less `ITU OB No. 1096`.
|
|
296
|
+
#
|
|
297
|
+
# @return [String]
|
|
294
298
|
def publication_code
|
|
295
|
-
"
|
|
299
|
+
return "ITU-R RR (#{ref.year})" if radio_regulations?
|
|
300
|
+
|
|
301
|
+
"ITU-T OB.#{ref.number} (#{ref.year})"
|
|
296
302
|
end
|
|
303
|
+
|
|
304
|
+
def radio_regulations?
|
|
305
|
+
ref.is_a? ::Pubid::Itu::Identifiers::RadioRegulations
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def bulletin? = ref.series.to_s == "OB"
|
|
297
309
|
end
|
|
298
310
|
end
|
|
299
311
|
end
|
data/lib/relaton/itu/scraper.rb
CHANGED
|
@@ -112,13 +112,23 @@ module Relaton
|
|
|
112
112
|
type = "ISO"
|
|
113
113
|
text.match(/[^(]+/).to_s.strip.squeeze(" ")
|
|
114
114
|
else
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
pubid.to_s
|
|
115
|
+
type = "ITU"
|
|
116
|
+
canonical_id text
|
|
118
117
|
end => id
|
|
119
118
|
Docidentifier.new(type: type, content: id, primary: true)
|
|
120
119
|
end
|
|
121
120
|
|
|
121
|
+
# The pubid spelling of an ITU code. The code is data from ITU, so a
|
|
122
|
+
# form pubid cannot identify is kept as ITU wrote it.
|
|
123
|
+
#
|
|
124
|
+
# @param text [String]
|
|
125
|
+
# @return [String]
|
|
126
|
+
def canonical_id(text)
|
|
127
|
+
::Pubid::Itu.parse(text).to_s
|
|
128
|
+
rescue ::Pubid::Errors::ParseError
|
|
129
|
+
text
|
|
130
|
+
end
|
|
131
|
+
|
|
122
132
|
# @return [Array<Relaton::Bib::Contributor>]
|
|
123
133
|
def fetch_contributors
|
|
124
134
|
contribs = fetch_publisher_contributors
|
data/lib/relaton/itu.rb
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require "mechanize"
|
|
2
|
-
require "parslet"
|
|
3
2
|
require "pubid"
|
|
4
3
|
require "digest/md5"
|
|
5
4
|
require "relaton/index"
|
|
@@ -11,7 +10,6 @@ require_relative "itu/item_data"
|
|
|
11
10
|
require_relative "itu/model/item"
|
|
12
11
|
require_relative "itu/model/bibitem"
|
|
13
12
|
require_relative "itu/model/bibdata"
|
|
14
|
-
require_relative "itu/pubid"
|
|
15
13
|
require_relative "itu/scraper"
|
|
16
14
|
require_relative "itu/hit_collection"
|
|
17
15
|
require_relative "itu/bibliography"
|
|
@@ -114,12 +114,12 @@ module Relaton
|
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
def parse_offset(resp) # rubocop:disable Metrics/AbcSize
|
|
117
|
-
if resp.
|
|
117
|
+
if resp.at_xpath('//*[@id="btnPaging"]') # first page
|
|
118
118
|
xpath = '//script[contains(.,"var count =")]'
|
|
119
|
-
@count = resp.
|
|
120
|
-
resp.
|
|
119
|
+
@count = resp.at_xpath(xpath).text.match(/var count = (\d+);/)[1]
|
|
120
|
+
resp.at_xpath("//*[@id='offset']")[:value].to_i
|
|
121
121
|
else
|
|
122
|
-
script = resp.
|
|
122
|
+
script = resp.at_xpath("//script").text
|
|
123
123
|
script.match(/\("offset"\)\.value = '(\d+)'/)[1].to_i
|
|
124
124
|
end
|
|
125
125
|
end
|