relaton 3.0.0.pre.alpha.1 → 3.0.0.pre.alpha.2
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/README.adoc +57 -1
- data/bin/console +0 -1
- data/lib/relaton/3gpp/bibliography.rb +82 -7
- data/lib/relaton/3gpp/data_fetcher.rb +51 -3
- data/lib/relaton/3gpp/docidentifier.rb +114 -0
- data/lib/relaton/3gpp/item.rb +6 -0
- data/lib/relaton/3gpp/parser.rb +1 -1
- data/lib/relaton/3gpp/processor.rb +4 -1
- data/lib/relaton/3gpp.rb +5 -1
- data/lib/relaton/adobe/bibdata.rb +8 -0
- data/lib/relaton/adobe/bibitem.rb +8 -0
- data/lib/relaton/adobe/bibliography.rb +92 -0
- data/lib/relaton/adobe/docidentifier.rb +49 -0
- data/lib/relaton/adobe/doctype.rb +14 -0
- data/lib/relaton/adobe/ext.rb +32 -0
- data/lib/relaton/adobe/item.rb +15 -0
- data/lib/relaton/adobe/item_base.rb +18 -0
- data/lib/relaton/adobe/item_data.rb +6 -0
- data/lib/relaton/adobe/processor.rb +45 -0
- data/lib/relaton/adobe/util.rb +8 -0
- data/lib/relaton/adobe.rb +37 -0
- data/lib/relaton/bib/model/address.rb +2 -2
- data/lib/relaton/bib/model/docidentifier.rb +24 -9
- data/lib/relaton/bib/model/localized_string.rb +1 -1
- data/lib/relaton/bib/model/structured_identifier.rb +10 -9
- data/lib/relaton/bib/sanitizer.rb +202 -6
- data/lib/relaton/bib.rb +0 -2
- data/lib/relaton/bipm/bibliography.rb +159 -10
- data/lib/relaton/bipm/data_fetcher.rb +26 -2
- data/lib/relaton/bipm/data_outcomes_parser.rb +3 -4
- data/lib/relaton/bipm/id_parser.rb +5 -4
- data/lib/relaton/bipm/model/structured_identifier.rb +21 -0
- data/lib/relaton/bipm/processor.rb +2 -2
- data/lib/relaton/bipm/rawdata_bipm_metrologia/fetcher.rb +2 -4
- data/lib/relaton/bipm/si_brochure_parser.rb +5 -3
- data/lib/relaton/bipm.rb +7 -1
- data/lib/relaton/bsi/bibliography.rb +115 -43
- data/lib/relaton/bsi/hit.rb +14 -0
- data/lib/relaton/bsi/hit_collection.rb +15 -16
- data/lib/relaton/bsi/model/docidentifier.rb +99 -1
- data/lib/relaton/bsi/processor.rb +1 -0
- data/lib/relaton/calconnect/bibliography.rb +12 -14
- data/lib/relaton/calconnect/data_fetcher.rb +77 -9
- data/lib/relaton/calconnect/docidentifier.rb +80 -0
- data/lib/relaton/calconnect/hit_collection.rb +65 -57
- data/lib/relaton/calconnect/model/item.rb +7 -0
- data/lib/relaton/calconnect/processor.rb +7 -1
- data/lib/relaton/calconnect.rb +11 -1
- data/lib/relaton/ccsds/data/fetcher.rb +17 -12
- data/lib/relaton/ccsds/data/parser.rb +1 -1
- data/lib/relaton/ccsds/hit_collection.rb +6 -1
- data/lib/relaton/ccsds/model/docidentifier.rb +121 -0
- data/lib/relaton/ccsds/model/item.rb +2 -0
- data/lib/relaton/cen/bibliography.rb +75 -47
- data/lib/relaton/cen/hit.rb +16 -1
- data/lib/relaton/cen/hit_collection.rb +59 -11
- data/lib/relaton/cen/model/docidentifier.rb +92 -1
- data/lib/relaton/cen/processor.rb +13 -8
- data/lib/relaton/cen/scraper.rb +13 -5
- data/lib/relaton/cen.rb +1 -0
- data/lib/relaton/cie/data_fetcher.rb +215 -30
- data/lib/relaton/cie/processor.rb +3 -1
- data/lib/relaton/cie/scrapper.rb +15 -2
- data/lib/relaton/cie.rb +2 -1
- data/lib/relaton/core/data_fetcher.rb +150 -3
- data/lib/relaton/core/governor.rb +320 -0
- data/lib/relaton/core/pacer.rb +134 -0
- data/lib/relaton/core/processor.rb +19 -0
- data/lib/relaton/core/request_error.rb +14 -0
- data/lib/relaton/core.rb +3 -0
- data/lib/relaton/db/registry.rb +41 -1
- data/lib/relaton/doi/crossref.rb +19 -2
- data/lib/relaton/doi/parser.rb +109 -15
- data/lib/relaton/easc/bibdata.rb +8 -0
- data/lib/relaton/easc/bibitem.rb +8 -0
- data/lib/relaton/easc/bibliography.rb +95 -0
- data/lib/relaton/easc/docidentifier.rb +100 -0
- data/lib/relaton/easc/doctype.rb +14 -0
- data/lib/relaton/easc/ext.rb +44 -0
- data/lib/relaton/easc/item.rb +13 -0
- data/lib/relaton/easc/item_base.rb +18 -0
- data/lib/relaton/easc/item_data.rb +6 -0
- data/lib/relaton/easc/processor.rb +46 -0
- data/lib/relaton/easc/util.rb +8 -0
- data/lib/relaton/easc.rb +35 -0
- data/lib/relaton/ecma/bibliography.rb +93 -25
- data/lib/relaton/ecma/data_fetcher.rb +71 -12
- data/lib/relaton/ecma/docidentifier.rb +124 -0
- data/lib/relaton/ecma/item.rb +2 -0
- data/lib/relaton/ecma/memento_parser.rb +1 -1
- data/lib/relaton/ecma/page_fetcher.rb +15 -3
- data/lib/relaton/ecma/parser_common.rb +2 -2
- data/lib/relaton/ecma/processor.rb +4 -1
- data/lib/relaton/ecma/standard_parser.rb +2 -2
- data/lib/relaton/ecma.rb +10 -1
- data/lib/relaton/etsi/bibliography.rb +67 -2
- data/lib/relaton/etsi/data_fetcher.rb +43 -4
- data/lib/relaton/etsi/processor.rb +3 -1
- data/lib/relaton/etsi.rb +2 -1
- data/lib/relaton/gb/bibliography.rb +55 -29
- data/lib/relaton/gb/docidentifier.rb +58 -9
- data/lib/relaton/gb/processor.rb +3 -0
- data/lib/relaton/gb/scraper.rb +27 -10
- data/lib/relaton/gost/bibdata.rb +8 -0
- data/lib/relaton/gost/bibitem.rb +8 -0
- data/lib/relaton/gost/bibliography.rb +107 -0
- data/lib/relaton/gost/docidentifier.rb +80 -0
- data/lib/relaton/gost/doctype.rb +16 -0
- data/lib/relaton/gost/ext.rb +46 -0
- data/lib/relaton/gost/item.rb +15 -0
- data/lib/relaton/gost/item_base.rb +18 -0
- data/lib/relaton/gost/item_data.rb +6 -0
- data/lib/relaton/gost/processor.rb +49 -0
- data/lib/relaton/gost/util.rb +8 -0
- data/lib/relaton/gost.rb +36 -0
- data/lib/relaton/iala/bibdata.rb +8 -0
- data/lib/relaton/iala/bibitem.rb +8 -0
- data/lib/relaton/iala/bibliography.rb +146 -0
- data/lib/relaton/iala/docidentifier.rb +89 -0
- data/lib/relaton/iala/doctype.rb +18 -0
- data/lib/relaton/iala/ext.rb +32 -0
- data/lib/relaton/iala/item.rb +21 -0
- data/lib/relaton/iala/item_base.rb +18 -0
- data/lib/relaton/iala/item_data.rb +6 -0
- data/lib/relaton/iala/processor.rb +43 -0
- data/lib/relaton/iala/relation.rb +7 -0
- data/lib/relaton/iala/util.rb +8 -0
- data/lib/relaton/iala.rb +35 -0
- data/lib/relaton/iana/bibliography.rb +67 -14
- data/lib/relaton/iana/data_fetcher.rb +35 -5
- data/lib/relaton/iana/processor.rb +3 -1
- data/lib/relaton/iana.rb +12 -1
- data/lib/relaton/iec/data_fetcher.rb +7 -1
- data/lib/relaton/iec/hit_collection.rb +1 -1
- data/lib/relaton/iec/model/docidentifier.rb +9 -5
- data/lib/relaton/iec/model/ext.rb +2 -2
- data/lib/relaton/iec/processor.rb +1 -0
- data/lib/relaton/ieee/bibliography.rb +25 -3
- data/lib/relaton/ieee/data_fetcher.rb +158 -17
- data/lib/relaton/ieee/idams_parser.rb +18 -11
- data/lib/relaton/ieee/processor.rb +4 -1
- data/lib/relaton/ieee/rawbib_id_parser.rb +291 -86
- data/lib/relaton/ieee.rb +2 -1
- data/lib/relaton/ietf/data_fetcher.rb +295 -12
- data/lib/relaton/ietf/processor.rb +7 -3
- data/lib/relaton/ietf/rfc/entry.rb +39 -3
- data/lib/relaton/ietf/scraper.rb +69 -36
- data/lib/relaton/ietf.rb +4 -1
- data/lib/relaton/iho/bibliography.rb +1 -1
- data/lib/relaton/iho/docidentifier.rb +1 -1
- data/lib/relaton/index/file_io.rb +11 -11
- data/lib/relaton/index/file_storage.rb +6 -1
- data/lib/relaton/index/pool.rb +6 -1
- data/lib/relaton/index/shard_source.rb +201 -0
- data/lib/relaton/index/type.rb +63 -12
- data/lib/relaton/index.rb +2 -1
- data/lib/relaton/iso/bibliography.rb +20 -15
- data/lib/relaton/iso/data_fetcher.rb +3 -3
- data/lib/relaton/iso/data_parser.rb +17 -3
- data/lib/relaton/iso/hit_collection.rb +27 -15
- data/lib/relaton/iso/item_data.rb +22 -0
- data/lib/relaton/iso/model/docidentifier.rb +24 -12
- data/lib/relaton/iso/processor.rb +1 -0
- data/lib/relaton/iso/scraper.rb +19 -3
- data/lib/relaton/itu/bibliography.rb +9 -4
- data/lib/relaton/itu/data_crawler_r.rb +664 -0
- data/lib/relaton/itu/data_fetcher.rb +496 -50
- data/lib/relaton/itu/data_merge_r.rb +149 -0
- data/lib/relaton/itu/data_parser_r.rb +163 -89
- data/lib/relaton/itu/data_parser_t.rb +228 -0
- data/lib/relaton/itu/family_cache.rb +177 -0
- data/lib/relaton/itu/governor.rb +56 -0
- data/lib/relaton/itu/hit.rb +9 -3
- data/lib/relaton/itu/hit_collection.rb +258 -86
- data/lib/relaton/itu/model/docidentifier.rb +67 -1
- data/lib/relaton/itu/model/structured_identifier.rb +19 -0
- data/lib/relaton/itu/processor.rb +10 -4
- data/lib/relaton/itu/pubid.rb +27 -5
- data/lib/relaton/itu/recommendation_fields.rb +334 -0
- data/lib/relaton/itu/recommendation_parser.rb +18 -149
- data/lib/relaton/itu/scraper.rb +13 -3
- data/lib/relaton/itu.rb +2 -1
- data/lib/relaton/jcgm/bibdata.rb +8 -0
- data/lib/relaton/jcgm/bibitem.rb +8 -0
- data/lib/relaton/jcgm/bibliography.rb +97 -0
- data/lib/relaton/jcgm/data_fetcher.rb +81 -0
- data/lib/relaton/jcgm/docidentifier.rb +102 -0
- data/lib/relaton/jcgm/doctype.rb +12 -0
- data/lib/relaton/jcgm/ext.rb +23 -0
- data/lib/relaton/jcgm/item.rb +20 -0
- data/lib/relaton/jcgm/item_base.rb +18 -0
- data/lib/relaton/jcgm/item_data.rb +6 -0
- data/lib/relaton/jcgm/meetings_parser.rb +175 -0
- data/lib/relaton/jcgm/processor.rb +71 -0
- data/lib/relaton/jcgm/relation.rb +9 -0
- data/lib/relaton/jcgm/structured_identifier.rb +40 -0
- data/lib/relaton/jcgm/util.rb +8 -0
- data/lib/relaton/jcgm.rb +24 -0
- data/lib/relaton/jis/bibliography.rb +8 -10
- data/lib/relaton/jis/data_fetcher.rb +21 -19
- data/lib/relaton/jis/docidentifier.rb +104 -5
- data/lib/relaton/jis/hit.rb +18 -23
- data/lib/relaton/jis/hit_collection.rb +19 -18
- data/lib/relaton/jis/processor.rb +1 -1
- data/lib/relaton/jis.rb +2 -3
- data/lib/relaton/logger/channels/gh_issue.rb +78 -13
- data/lib/relaton/nist/data_fetcher.rb +63 -13
- data/lib/relaton/nist/docidentifier.rb +165 -0
- data/lib/relaton/nist/item.rb +2 -0
- data/lib/relaton/nist/item_base.rb +16 -0
- data/lib/relaton/nist/mods_parser.rb +38 -12
- data/lib/relaton/nist/processor.rb +2 -1
- data/lib/relaton/nist/relation.rb +3 -0
- data/lib/relaton/nist/scraper.rb +6 -3
- data/lib/relaton/oasis/bibliography.rb +147 -6
- data/lib/relaton/oasis/data_fetcher.rb +41 -5
- data/lib/relaton/oasis/data_parser_utils.rb +37 -3
- data/lib/relaton/oasis/docidentifier.rb +54 -0
- data/lib/relaton/oasis/item.rb +3 -0
- data/lib/relaton/oasis/processor.rb +7 -1
- data/lib/relaton/oasis.rb +14 -1
- data/lib/relaton/ogc/data_fetcher.rb +23 -2
- data/lib/relaton/ogc/docidentifier.rb +105 -0
- data/lib/relaton/ogc/hit_collection.rb +78 -3
- data/lib/relaton/ogc/processor.rb +2 -1
- data/lib/relaton/ogc.rb +5 -1
- data/lib/relaton/oiml/bibliography.rb +90 -15
- data/lib/relaton/oiml/docidentifier.rb +18 -3
- data/lib/relaton/omg/docidentifier.rb +67 -0
- data/lib/relaton/omg/item.rb +1 -0
- data/lib/relaton/omg/processor.rb +1 -0
- data/lib/relaton/omg/scraper.rb +61 -16
- data/lib/relaton/omg.rb +1 -0
- data/lib/relaton/plateau/bibliography.rb +10 -3
- data/lib/relaton/plateau/data_fetcher.rb +25 -2
- data/lib/relaton/plateau/handbook_parser.rb +8 -1
- data/lib/relaton/plateau/hit.rb +10 -2
- data/lib/relaton/plateau/hit_collection.rb +31 -11
- data/lib/relaton/plateau/processor.rb +3 -1
- data/lib/relaton/plateau/technical_report_parser.rb +8 -1
- data/lib/relaton/plateau.rb +2 -1
- data/lib/relaton/sdo/config.rb +34 -0
- data/lib/relaton/sdo/fetcher.rb +52 -0
- data/lib/relaton/sdo/logo.rb +95 -0
- data/lib/relaton/sdo/name.rb +26 -0
- data/lib/relaton/sdo/organization.rb +71 -0
- data/lib/relaton/sdo/store.rb +49 -0
- data/lib/relaton/sdo.rb +29 -0
- data/lib/relaton/version.rb +1 -1
- data/lib/relaton/w3c/bibliography.rb +132 -12
- data/lib/relaton/w3c/data_fetcher.rb +194 -16
- data/lib/relaton/w3c/data_parser.rb +3 -3
- data/lib/relaton/w3c/docidentifier.rb +48 -0
- data/lib/relaton/w3c/governor.rb +32 -0
- data/lib/relaton/w3c/item.rb +3 -0
- data/lib/relaton/w3c/pubid.rb +12 -0
- data/lib/relaton/w3c/safe_realize.rb +110 -21
- data/lib/relaton/w3c.rb +12 -1
- data/lib/relaton/xsf/bibliography.rb +61 -1
- data/lib/relaton/xsf/data_fetcher.rb +55 -5
- data/lib/relaton/xsf/docidentifier.rb +46 -0
- data/lib/relaton/xsf/hit_collection.rb +31 -3
- data/lib/relaton/xsf/item.rb +6 -0
- data/lib/relaton/xsf/processor.rb +1 -0
- data/lib/relaton/xsf.rb +5 -1
- data/lib/relaton.rb +42 -0
- metadata +135 -24
- data/lib/relaton/ieee/pub_id.rb +0 -161
- data/lib/relaton/index/id_number.rb +0 -30
|
@@ -1,8 +1,74 @@
|
|
|
1
1
|
module Relaton
|
|
2
2
|
module Itu
|
|
3
|
+
# An ITU document identifier, backed by `Pubid::Itu`.
|
|
4
|
+
#
|
|
5
|
+
# `content` stays the source string for serialization. `#pubid` parses it
|
|
6
|
+
# into a `Pubid::Itu::Identifier`, and `#remove_date!` changes that pubid
|
|
7
|
+
# and renders it back into `content`. There is no string surgery.
|
|
8
|
+
#
|
|
9
|
+
# ## Why the parse is lazy
|
|
10
|
+
#
|
|
11
|
+
# Most ITU records also carry an `ISO` co-identifier ("ISO/IEC 14496-10"),
|
|
12
|
+
# which never parses with `Pubid::Itu`. An eager parse in `content=` would
|
|
13
|
+
# run a failed Parslet parse on every deserialization. So `#pubid` parses on
|
|
14
|
+
# first use and keeps the result until `content` changes.
|
|
15
|
+
#
|
|
16
|
+
# The parse is soft: content that does not parse gives a nil `#pubid`, with
|
|
17
|
+
# no log, because an ISO co-identifier is not a defect. With a nil `#pubid`
|
|
18
|
+
# the mutators do nothing.
|
|
3
19
|
class Docidentifier < Bib::Docidentifier
|
|
20
|
+
# Capture the inherited content setter before the override, so that a
|
|
21
|
+
# mutation can write the rendered string back and keep the changed pubid.
|
|
22
|
+
# `content=` would clear it, and a new parse would lose nothing here, but
|
|
23
|
+
# it would parse again for no reason.
|
|
24
|
+
alias_method :store_content, :content=
|
|
25
|
+
|
|
26
|
+
def content=(value)
|
|
27
|
+
super
|
|
28
|
+
@pubid = nil
|
|
29
|
+
@pubid_parsed = false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Pubid::Itu::Identifier, nil] nil if the content does not parse
|
|
33
|
+
def pubid
|
|
34
|
+
return @pubid if @pubid_parsed
|
|
35
|
+
|
|
36
|
+
@pubid_parsed = true
|
|
37
|
+
@pubid = parse
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Removes the year and the month from the identifier and from every
|
|
41
|
+
# identifier in its `base` chain, because `Pubid::Identifier#exclude`
|
|
42
|
+
# recurses into `base`:
|
|
43
|
+
#
|
|
44
|
+
# ITU-T L.163 (11/2018) -> ITU-T L.163
|
|
45
|
+
# ITU-T H.264 (2005) Amd. 1 (06/2006) -> ITU-T H.264 Amd. 1
|
|
46
|
+
#
|
|
47
|
+
# The version stays: `ITU-T H.264 (V14) (08/2021)` -> `ITU-T H.264 (V14)`.
|
|
48
|
+
# It is not a date, and two versions are two documents.
|
|
49
|
+
#
|
|
50
|
+
# The result is the pubid canonical spelling, so a source `v10` becomes
|
|
51
|
+
# `(V10)`. `Pubid::Itu` has no `year=`/`month=` setters, so `#exclude` is
|
|
52
|
+
# the only way to clear them.
|
|
53
|
+
#
|
|
54
|
+
# `remove_part!` and `to_all_parts!` stay the inherited no-ops: the `-3`
|
|
55
|
+
# of `ITU-R P.838-3` is a revision, not a part.
|
|
4
56
|
def remove_date!
|
|
5
|
-
|
|
57
|
+
return unless pubid
|
|
58
|
+
|
|
59
|
+
@pubid = @pubid.exclude(:year, :month)
|
|
60
|
+
store_content @pubid.to_s
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def parse
|
|
66
|
+
return unless content
|
|
67
|
+
|
|
68
|
+
require "pubid"
|
|
69
|
+
::Pubid::Itu.parse content.to_s
|
|
70
|
+
rescue LoadError, StandardError
|
|
71
|
+
nil
|
|
6
72
|
end
|
|
7
73
|
end
|
|
8
74
|
end
|
|
@@ -14,6 +14,25 @@ module Relaton
|
|
|
14
14
|
map_element "amendment", to: :amendment
|
|
15
15
|
map_element "corrigendum", to: :corrigendum
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
# `Bib::ItemData#ext_to_all_parts!` / `#ext_remove_date` broadcast to
|
|
19
|
+
# `ext.structuredidentifier` as well as to `docidentifier`. This class
|
|
20
|
+
# descends from `Lutaml::Model::Serializable` rather than
|
|
21
|
+
# `Bib::StructuredIdentifier`, so it does not inherit that class's no-op
|
|
22
|
+
# defaults and the broadcast raised `NoMethodError` on every ITU item.
|
|
23
|
+
#
|
|
24
|
+
# ITU structured identifiers model neither a part nor a date: `bureau`,
|
|
25
|
+
# `docnumber`, `annexid`, `amendment` and `corrigendum` are all identity,
|
|
26
|
+
# and stripping any of them would name a different document. The date is
|
|
27
|
+
# handled by `Relaton::Itu::Docidentifier#remove_date!`, which removes the
|
|
28
|
+
# year and month from the docid's pubid and renders it again. So all
|
|
29
|
+
# three are no-ops.
|
|
30
|
+
|
|
31
|
+
def remove_part!; end
|
|
32
|
+
|
|
33
|
+
def remove_date!; end
|
|
34
|
+
|
|
35
|
+
def to_all_parts!; end
|
|
17
36
|
end
|
|
18
37
|
end
|
|
19
38
|
end
|
|
@@ -8,7 +8,7 @@ module Relaton
|
|
|
8
8
|
@prefix = "ITU"
|
|
9
9
|
@defaultprefix = %r{^ITU\s}
|
|
10
10
|
@idtype = "ITU"
|
|
11
|
-
@datasets = %w[itu-r]
|
|
11
|
+
@datasets = %w[itu-r itu-t]
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
# @param code [String]
|
|
@@ -21,9 +21,13 @@ module Relaton
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
#
|
|
24
|
-
#
|
|
24
|
+
# Harvest a dataset. Only "itu-t" harvests (the searchRecs corpus); "itu-r"
|
|
25
|
+
# raises — its bulk-enumeration endpoint is gone (issue #75) — and it stays
|
|
26
|
+
# in @datasets on purpose, so a stray `relaton fetch-data itu-r` surfaces
|
|
27
|
+
# that error instead of the registry's warn-and-exit-0 for an unknown
|
|
28
|
+
# dataset.
|
|
25
29
|
#
|
|
26
|
-
# @param [String] source source name (itu-r)
|
|
30
|
+
# @param [String] source source name (itu-t; itu-r raises)
|
|
27
31
|
# @param [Hash] opts
|
|
28
32
|
# @option opts [String] :output directory to output documents, default is data
|
|
29
33
|
# @option opts [String] :format output format, default is yaml
|
|
@@ -59,7 +63,9 @@ module Relaton
|
|
|
59
63
|
#
|
|
60
64
|
def remove_index_file
|
|
61
65
|
require_relative "../itu"
|
|
62
|
-
Relaton::Index.find_or_create(
|
|
66
|
+
Relaton::Index.find_or_create(
|
|
67
|
+
:itu, url: true, file: "#{INDEXFILE}.yaml"
|
|
68
|
+
).remove_file
|
|
63
69
|
end
|
|
64
70
|
end
|
|
65
71
|
end
|
data/lib/relaton/itu/pubid.rb
CHANGED
|
@@ -11,7 +11,15 @@ module Relaton
|
|
|
11
11
|
|
|
12
12
|
rule(:prefix) { str("ITU").as(:prefix) }
|
|
13
13
|
rule(:sector) { separator >> match("[A-Z]").as(:sector) }
|
|
14
|
-
|
|
14
|
+
# "Report" is not decoration: ITU-R Recommendations and Reports number
|
|
15
|
+
# independently, so "ITU-R BT.2020-1" alone names two different documents
|
|
16
|
+
# (Rec. BT.2020-1, 06/2014 vs Report BT.2020-1, 2000). ITU writes the
|
|
17
|
+
# discriminator first ("Report ITU-R BT.2020-1"), which is the form
|
|
18
|
+
# `Pubid::Itu` parses as `pubid:itu:report`; the sector-first spelling is
|
|
19
|
+
# accepted too because bibliographies use both.
|
|
20
|
+
rule(:report) { (str("Report") | str("REP")).as(:type) >> space }
|
|
21
|
+
rule(:report?) { report.maybe }
|
|
22
|
+
rule(:type) { separator >> (str("REC") | str("Report") | str("REP")).as(:type) }
|
|
15
23
|
rule(:type?) { type.maybe }
|
|
16
24
|
rule(:code) { separator >> (match["A-Z0-9"].repeat(1) >> match["[:alnum:]/.-"].repeat).as(:code) }
|
|
17
25
|
rule(:year) { (match["12"] >> num.repeat(3, 3)).as(:year) }
|
|
@@ -41,7 +49,7 @@ module Relaton
|
|
|
41
49
|
|
|
42
50
|
rule(:itu_pubid_sector) { prefix >> sector >> type? >> code >> sup? >> annex? >> ver? >> date? >> amd? >> any.repeat }
|
|
43
51
|
rule(:itu_pubid_no_sector) { prefix >> type? >> code >> sup? >> annex? >> ver? >> date? >> amd? >> any.repeat }
|
|
44
|
-
rule(:itu_pubid) { itu_pubid_sector | itu_pubid_no_sector }
|
|
52
|
+
rule(:itu_pubid) { report? >> (itu_pubid_sector | itu_pubid_no_sector) }
|
|
45
53
|
root(:itu_pubid)
|
|
46
54
|
end
|
|
47
55
|
|
|
@@ -74,7 +82,11 @@ module Relaton
|
|
|
74
82
|
rescue Parslet::ParseFailed => e
|
|
75
83
|
Util.error "`#{id}` is invalid ITU publication identifier\n" \
|
|
76
84
|
"#{e.parse_failure_cause.ascii_tree}"
|
|
77
|
-
raise
|
|
85
|
+
# This grammar is local, so re-raise it as pubid's error: relaton-cli
|
|
86
|
+
# rescues `Pubid::Errors::Error`. `::` is needed, because inside this
|
|
87
|
+
# class a bare `Pubid` is `Relaton::Itu::Pubid`.
|
|
88
|
+
raise ::Pubid::Errors::ParseError.new(e.message, e.parse_failure_cause,
|
|
89
|
+
input: id, flavor: "itu")
|
|
78
90
|
end
|
|
79
91
|
|
|
80
92
|
def to_h(with_type: true) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
@@ -96,10 +108,20 @@ module Relaton
|
|
|
96
108
|
to_s ref: true
|
|
97
109
|
end
|
|
98
110
|
|
|
111
|
+
# @return [Boolean] whether this reference names an ITU-R Report rather
|
|
112
|
+
# than a Recommendation — the one type that must survive into #to_ref,
|
|
113
|
+
# because it is what tells the two apart (see the `report` parser rule).
|
|
114
|
+
def report?
|
|
115
|
+
%w[Report REP].include? type
|
|
116
|
+
end
|
|
117
|
+
|
|
99
118
|
def to_s(ref: false) # rubocop:disable Metrics/AbcSize
|
|
100
|
-
|
|
119
|
+
# "Report" leads, the way ITU cites it and the way Pubid::Itu parses it;
|
|
120
|
+
# `REC` stays dropped from a reference because it is redundant there.
|
|
121
|
+
s = report? ? +"Report " : +""
|
|
122
|
+
s << prefix
|
|
101
123
|
s << "-#{sector}" if sector
|
|
102
|
-
s << " #{type}" if type && !ref
|
|
124
|
+
s << " #{type}" if type && !ref && !report?
|
|
103
125
|
s << " #{code}"
|
|
104
126
|
s << " Suppl. #{suppl}" if suppl
|
|
105
127
|
s << " Annex #{annex}" if annex
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "family_cache"
|
|
5
|
+
|
|
6
|
+
module Relaton
|
|
7
|
+
module Itu
|
|
8
|
+
# Shared extraction of ITU-T Recommendation metadata from the
|
|
9
|
+
# `mws/api/recommendations/*` detail endpoints. Reused by BOTH the live
|
|
10
|
+
# lookup path (`RecommendationParser`, via `Scraper`) and the offline
|
|
11
|
+
# harvester (`DataParserT`), so producer output and runtime output stay
|
|
12
|
+
# identical field-for-field.
|
|
13
|
+
#
|
|
14
|
+
# The includer must provide four hooks:
|
|
15
|
+
# #agent — a Mechanize agent (browser UA; www.itu.int is behind an F5 WAF)
|
|
16
|
+
# #idrec — the recommendation record id
|
|
17
|
+
# #imp — whether this is an Implementers' Guide (live-only; harvester false)
|
|
18
|
+
# #cache — a cross-row cache, defaulting to NullCache (see below)
|
|
19
|
+
#
|
|
20
|
+
# `#doc` (the getRecHdrDetail JSON) is memoised here from those hooks.
|
|
21
|
+
#
|
|
22
|
+
# **The cache and the runtime boundary.** `searchRecs` returns one row per
|
|
23
|
+
# *edition*, but two of the four endpoints below answer for the whole
|
|
24
|
+
# recommendation, so the offline harvester asks the same question once per
|
|
25
|
+
# edition — 21 times over for H.264. `#cache` lets it ask once instead. The
|
|
26
|
+
# live lookup path handles one recommendation per `Bibliography.get` and must
|
|
27
|
+
# not share state between calls, so the default is {NullCache}, whose
|
|
28
|
+
# `#fetch` is a bare yield: same behaviour, same requests, nothing retained.
|
|
29
|
+
# `Scraper` is deliberately left calling the three-argument constructor, so
|
|
30
|
+
# no path from `Bibliography.get` can reach a real cache.
|
|
31
|
+
module RecommendationFields
|
|
32
|
+
include Relaton::Core::ArrayWrapper
|
|
33
|
+
|
|
34
|
+
RECHDR = "https://www.itu.int/mws/api/recommendations/getRecHdrDetail?idrec=%{idrec}&lang=en"
|
|
35
|
+
RECEDITIONS = "https://www.itu.int/mws/api/recommendations/getRecEditions?idrec=%{idrec}&lang=en"
|
|
36
|
+
RECSUPPLEMENTS = "https://www.itu.int/mws/api/recommendations/getRecSupplements?idrec=%{idrec}&lang=en"
|
|
37
|
+
IMPLGUIDES = "https://www.itu.int/mws/api/recommendations/getImplGuides?idrec=%{idrec}&lang=en"
|
|
38
|
+
|
|
39
|
+
# The recommendation header detail (getRecHdrDetail / getImplGuides).
|
|
40
|
+
# @return [Hash]
|
|
41
|
+
def doc
|
|
42
|
+
@doc ||= begin
|
|
43
|
+
url = (imp ? IMPLGUIDES : RECHDR) % { idrec: idrec }
|
|
44
|
+
resp = get_data url
|
|
45
|
+
imp ? resp.first : resp
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Cross-row cache for the family-invariant endpoints. Overridden by
|
|
50
|
+
# RecommendationParser when the harvester passes one in.
|
|
51
|
+
# @return [#fetch, #warm]
|
|
52
|
+
def cache
|
|
53
|
+
NullCache.instance
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Canonical id for the recommendation this edition belongs to: the
|
|
57
|
+
# smallest idrec ITU's own getRecEditions response lists.
|
|
58
|
+
#
|
|
59
|
+
# Derived from API output, never from parsing `rec_name`. That is the
|
|
60
|
+
# whole point — two recommendations whose names happen to normalize alike
|
|
61
|
+
# would silently share each other's metadata, and nothing downstream would
|
|
62
|
+
# notice (the data repo's `guard_enrichment` counts `contributor:` lines,
|
|
63
|
+
# which such a record still has).
|
|
64
|
+
#
|
|
65
|
+
# Free on the live path: `Scraper#parse_page` already calls
|
|
66
|
+
# `#fetch_relations`, which already calls `#editions`.
|
|
67
|
+
#
|
|
68
|
+
# @return [Integer, String]
|
|
69
|
+
def family_id
|
|
70
|
+
@family_id ||= (editions.filter_map { |e| e["idrec"] }.min || idrec)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [String, nil]
|
|
74
|
+
def fetch_edition
|
|
75
|
+
self_edition&.dig("Version")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [Array<Relaton::Bib::Title>]
|
|
79
|
+
def fetch_titles
|
|
80
|
+
title = imp ? doc["imp_title_e"] : doc["rec_title"]
|
|
81
|
+
return [] if title.nil? || title.empty?
|
|
82
|
+
|
|
83
|
+
Relaton::Bib::Title.from_string title, "en", "Latn"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [Relaton::Bib::Status, nil]
|
|
87
|
+
def fetch_status
|
|
88
|
+
inforce = imp ? imp_status : doc["status"]
|
|
89
|
+
return if inforce.nil? || inforce.empty?
|
|
90
|
+
|
|
91
|
+
status = inforce == "In force" ? "Published" : "Withdrawal"
|
|
92
|
+
Relaton::Bib::Status.new(stage: Relaton::Bib::Status::Stage.new(content: status))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [Array<Relaton::Bib::Date>]
|
|
96
|
+
def fetch_dates
|
|
97
|
+
array(doc_date).map { |on| Relaton::Bib::Date.new(type: "published", at: on) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @return [Array<Relaton::Bib::Abstract>]
|
|
101
|
+
def fetch_abstract
|
|
102
|
+
array(doc["summary"]).map do |content|
|
|
103
|
+
Relaton::Bib::Abstract.new(content: content, language: "en", script: "Latn")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @return [Array<Relaton::Bib::Uri>]
|
|
108
|
+
def fetch_source
|
|
109
|
+
link = imp ? doc["imp_dms_link"] : doc["handle_id"]
|
|
110
|
+
links = [Relaton::Bib::Uri.new(type: "src", content: link)]
|
|
111
|
+
links << Relaton::Bib::Uri.new(type: "pdf", content: doc["handle_id_pdf_link"]) if doc["handle_id_pdf_link"]
|
|
112
|
+
imp_word_link { |wlink| links << Relaton::Bib::Uri.new(type: "word", content: wlink) }
|
|
113
|
+
links
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def doc_date
|
|
117
|
+
return @doc_date if defined? @doc_date
|
|
118
|
+
|
|
119
|
+
date = imp ? doc["imp_approval_date"] : doc["approval_date"]
|
|
120
|
+
@doc_date = Date.parse(date).to_s rescue date # rubocop:disable Style/RescueModifier
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @return [Array<Relaton::Bib::Relation>]
|
|
124
|
+
def fetch_relations
|
|
125
|
+
relations = []
|
|
126
|
+
editions.each do |ed|
|
|
127
|
+
next if ed["idrec"] == idrec
|
|
128
|
+
|
|
129
|
+
relations << create_relation("hasEdition", ed["title"], ed["rec_name"])
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
supplements.each { |supp| relations << create_relation("complementOf", supp["title_text"], supp["rec_name"]) }
|
|
133
|
+
relations
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# The equivalent ISO/IEC(/IEEE) identifier, from the recommendation
|
|
137
|
+
# header's `iso_number` (e.g. "ISO/IEC 17788:2014 (Common)", or a deliverable
|
|
138
|
+
# form like "ISO/IEC TR 29110"). Used as an additional docidentifier. Only
|
|
139
|
+
# recommendations carry it. The optional TR/TS/PAS/IWA/Guide token keeps
|
|
140
|
+
# deliverable forms that a bare `\d` anchor would drop.
|
|
141
|
+
# @return [Relaton::Itu::Docidentifier, nil]
|
|
142
|
+
def iso_docid
|
|
143
|
+
num = doc["iso_number"]
|
|
144
|
+
id = num && num[%r{ISO(?:/IEC)?(?:/IEEE)?(?:\s+(?:TR|TS|PAS|IWA|Guide))?\s+\d[\d-]*}]
|
|
145
|
+
Docidentifier.new(type: "ISO", content: id, primary: true) if id
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The editorial-group (study group / TSAG) contributor, from the
|
|
149
|
+
# recommendation HTML page's workgroup name.
|
|
150
|
+
# @param bureau [String, nil] the sector letter, e.g. "T" (from ITU-T)
|
|
151
|
+
# @return [Relaton::Bib::Contributor, nil]
|
|
152
|
+
def editorial_group(bureau)
|
|
153
|
+
return unless bureau
|
|
154
|
+
|
|
155
|
+
org = Relaton::Bib::Organization.new(
|
|
156
|
+
name: [Relaton::Bib::TypedLocalizedString.new(content: "International Telecommunication Union")],
|
|
157
|
+
abbreviation: Relaton::Bib::LocalizedString.new(content: "ITU-#{bureau.upcase}"),
|
|
158
|
+
subdivision: group_subdivision(fetch_workgroup),
|
|
159
|
+
)
|
|
160
|
+
role = Relaton::Bib::Contributor::Role.new(
|
|
161
|
+
type: "author",
|
|
162
|
+
description: [Relaton::Bib::LocalizedMarkedUpString.new(content: "committee")],
|
|
163
|
+
)
|
|
164
|
+
Relaton::Bib::Contributor.new(organization: org, role: [role])
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# The publisher contributor (ITU) for the given abbreviation.
|
|
168
|
+
# @param abbrev [String, nil] e.g. "ITU"
|
|
169
|
+
# @return [Relaton::Bib::Contributor, nil]
|
|
170
|
+
def publisher(abbrev)
|
|
171
|
+
return unless abbrev == "ITU"
|
|
172
|
+
|
|
173
|
+
org = Relaton::Bib::Organization.new(
|
|
174
|
+
name: [Relaton::Bib::TypedLocalizedString.new(content: "International Telecommunication Union")],
|
|
175
|
+
abbreviation: Relaton::Bib::LocalizedString.new(content: abbrev),
|
|
176
|
+
uri: [Relaton::Bib::Uri.new(content: "www.itu.int")],
|
|
177
|
+
)
|
|
178
|
+
role = Relaton::Bib::Contributor::Role.new(type: "publisher")
|
|
179
|
+
Relaton::Bib::Contributor.new(organization: org, role: [role])
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @param wg_name [String, nil]
|
|
183
|
+
# @return [Array<Relaton::Bib::Subdivision>]
|
|
184
|
+
def group_subdivision(wg_name)
|
|
185
|
+
return [] unless wg_name
|
|
186
|
+
|
|
187
|
+
subtype = case wg_name
|
|
188
|
+
when /Advisory Group/ then "tsag"
|
|
189
|
+
when /Study Group/ then "study-group"
|
|
190
|
+
else "work-group"
|
|
191
|
+
end
|
|
192
|
+
[Relaton::Bib::Subdivision.new(
|
|
193
|
+
type: "technical-committee",
|
|
194
|
+
subtype: subtype,
|
|
195
|
+
name: [Relaton::Bib::TypedLocalizedString.new(content: wg_name)],
|
|
196
|
+
)]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Fetch the study group name from the recommendation HTML page.
|
|
200
|
+
#
|
|
201
|
+
# The single most expensive request per record: `rec.aspx` is ~90-240 KB
|
|
202
|
+
# of HTML that then has to be parsed into a DOM. It is also the one field
|
|
203
|
+
# here that is a property of the *recommendation* rather than the edition
|
|
204
|
+
# — ITU renders the current owning study group even on an old edition's
|
|
205
|
+
# page, which is why `?rec=H.264` and `?rec=14659` both yield "ITU-T Study
|
|
206
|
+
# Group 21" in `spec/itu/vcr_cassettes/itu_t_h_264.yml`. So it is cached
|
|
207
|
+
# per family.
|
|
208
|
+
#
|
|
209
|
+
# The rescue sits outside the cache on purpose: a transient failure must
|
|
210
|
+
# not be stored as nil for the whole family. FamilyCache does not mark an
|
|
211
|
+
# entry computed when the block raises, so the next sibling retries.
|
|
212
|
+
# @return [String, nil]
|
|
213
|
+
def fetch_workgroup
|
|
214
|
+
# Short-circuited rather than keyed-then-yielded, because #family_id
|
|
215
|
+
# reads #editions: with the null cache that would add a getRecEditions
|
|
216
|
+
# request to a runtime lookup that did not need one. The live path must
|
|
217
|
+
# keep exactly the request count it had.
|
|
218
|
+
return workgroup_from_page unless cache.caching?
|
|
219
|
+
|
|
220
|
+
cache.fetch([:workgroup, family_id]) { workgroup_from_page }
|
|
221
|
+
rescue StandardError
|
|
222
|
+
nil
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private
|
|
226
|
+
|
|
227
|
+
def workgroup_from_page
|
|
228
|
+
url = "https://www.itu.int/ITU-T/recommendations/rec.aspx?rec=#{idrec}&lang=en"
|
|
229
|
+
page = agent.get(url)
|
|
230
|
+
wg = page.at('//span[contains(@id, "uc_rec_main_info1_rpt_main_ctl00_Label8")]/a')
|
|
231
|
+
wg&.text
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def fetch_editions
|
|
235
|
+
get_data(RECEDITIONS % { idrec: idrec }) || []
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# The other editions this response answers for.
|
|
239
|
+
#
|
|
240
|
+
# Empty unless the response actually lists the idrec we asked for: if it
|
|
241
|
+
# does not, the endpoint's contract has changed under us and sharing the
|
|
242
|
+
# payload would hand a whole family another recommendation's data. Losing
|
|
243
|
+
# the dedup is the cheap failure; sharing wrongly is the expensive one.
|
|
244
|
+
def siblings_of(rows)
|
|
245
|
+
unless rows.any? { |r| r["idrec"] == idrec }
|
|
246
|
+
Util.warn "ITU-T: getRecEditions(#{idrec}) does not list #{idrec}; not sharing it across the family"
|
|
247
|
+
return []
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
rows.filter_map { |r| [:editions, r["idrec"]] unless r["idrec"] == idrec }
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def get_data(url)
|
|
254
|
+
JSON.parse request_document(url).body
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def request_document(url)
|
|
258
|
+
agent.get url
|
|
259
|
+
rescue Mechanize::ResponseCodeError, SocketError, Timeout::Error, Errno::ECONNRESET,
|
|
260
|
+
EOFError, Net::ProtocolError, OpenSSL::SSL::SSLError => e
|
|
261
|
+
raise Relaton::RequestError, "Could not access #{url}: #{e.message}"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Every edition of this recommendation, newest first.
|
|
265
|
+
#
|
|
266
|
+
# One request answers the whole family, and the response says so itself:
|
|
267
|
+
# it lists every sibling `idrec`, and the payload for each of them is the
|
|
268
|
+
# same one — verified byte-for-byte for 16818 and 14659 in
|
|
269
|
+
# `spec/itu/vcr_cassettes/itu_t_h_264.yml`. So the result is published
|
|
270
|
+
# under every sibling key, and the family's other 20 editions cost nothing.
|
|
271
|
+
def editions
|
|
272
|
+
@editions ||= begin
|
|
273
|
+
rows = cache.fetch([:editions, idrec]) { fetch_editions }
|
|
274
|
+
# Warmed after #fetch released the entry lock, so there is no
|
|
275
|
+
# lock-order inversion between the table lock and an entry lock.
|
|
276
|
+
cache.warm(siblings_of(rows), rows)
|
|
277
|
+
rows
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def self_edition
|
|
282
|
+
@self_edition ||= editions.find { |ed| ed["idrec"] == idrec }
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def imp_status
|
|
286
|
+
self_edition&.dig("status")
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def imp_word_link
|
|
290
|
+
return unless doc["imp_dms_link"]
|
|
291
|
+
|
|
292
|
+
@doc_page ||= request_document(doc["imp_dms_link"])
|
|
293
|
+
wrd_elm = @doc_page.at("//font[contains(.,'Word')]/../..")
|
|
294
|
+
yield wrd_elm[:href] if block_given? && wrd_elm
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def create_relation(type, title_text, id)
|
|
298
|
+
titles = []
|
|
299
|
+
if title_text && !title_text.empty?
|
|
300
|
+
titles << Relaton::Bib::Title.new(content: title_text, language: "en", script: "Latn")
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
fref = titles.empty? ? id : nil
|
|
304
|
+
did = Relaton::Bib::Docidentifier.new(type: "ITU", content: id, primary: true)
|
|
305
|
+
bibitem = Relaton::Bib::ItemData.new(title: titles, formattedref: (fref ? Relaton::Bib::Formattedref.new(content: fref) : nil), docidentifier: [did])
|
|
306
|
+
Relaton::Bib::Relation.new(type: type, bibitem: bibitem)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Supplements to this recommendation.
|
|
310
|
+
#
|
|
311
|
+
# **Deliberately not shared across the family**, unlike #editions and
|
|
312
|
+
# #fetch_workgroup. It very probably is family-invariant — supplements
|
|
313
|
+
# belong to a recommendation, not to an edition — but that is an
|
|
314
|
+
# assumption, and the only recording in the suite is a single
|
|
315
|
+
# `getRecSupplements?idrec=14659`. Sharing it wrongly would give a
|
|
316
|
+
# record another recommendation's `relation:` list, silently: the data
|
|
317
|
+
# repo's `guard_enrichment` counts `contributor:` lines, which a wrongly
|
|
318
|
+
# enriched record still has. What would settle it: add a
|
|
319
|
+
# `getRecSupplements?idrec=16818` interaction to
|
|
320
|
+
# `spec/itu/vcr_cassettes/itu_t_h_264.yml` and assert the two payloads
|
|
321
|
+
# are equal; then key this on #family_id like the other two.
|
|
322
|
+
def supplements
|
|
323
|
+
@supplements ||= begin
|
|
324
|
+
if imp
|
|
325
|
+
[]
|
|
326
|
+
else
|
|
327
|
+
url = RECSUPPLEMENTS % { idrec: idrec }
|
|
328
|
+
get_data(url) || []
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|