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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
require_relative "../itu"
|
|
2
|
+
|
|
3
|
+
module Relaton
|
|
4
|
+
module Itu
|
|
5
|
+
# Incremental write path for harvested ITU-R records (issue #75).
|
|
6
|
+
#
|
|
7
|
+
# The ITU-R dataset cannot be rebuilt from scratch — DataCrawlerR reads the
|
|
8
|
+
# pages ITU still serves, and those expose **less** than the decommissioned
|
|
9
|
+
# RunSearch feed did. Two consequences drive this module:
|
|
10
|
+
#
|
|
11
|
+
# 1. **A published date must never be rewritten.** A Recommendation page
|
|
12
|
+
# carries only its approval date, while the preserved record holds the
|
|
13
|
+
# publication date RunSearch served — measured on the whole BO series,
|
|
14
|
+
# those differ for every record and the *year* differs for 59% of them.
|
|
15
|
+
# Overwriting would change which edition answers a dated reference. (A
|
|
16
|
+
# Report page does carry the publication date, so reports reproduce it;
|
|
17
|
+
# the rule costs nothing there.)
|
|
18
|
+
# 2. **A harvest is a partial view.** Only the families the crawler
|
|
19
|
+
# implements are seen, so a run must add and backfill, never delete, and
|
|
20
|
+
# must leave untouched records byte-identical so the data repo's diff
|
|
21
|
+
# shows only what actually changed.
|
|
22
|
+
#
|
|
23
|
+
# Merge rules, applied per record: `date` is kept as published, always;
|
|
24
|
+
# `title` and `source` are filled in only when the published record has
|
|
25
|
+
# none; everything else is left alone. A record whose published counterpart
|
|
26
|
+
# has a **different doctype** is a collision — an ITU-R report and
|
|
27
|
+
# recommendation in the same series can share a docidentifier, and therefore
|
|
28
|
+
# a filename — so it is reported and skipped rather than silently
|
|
29
|
+
# overwriting the other document.
|
|
30
|
+
module DataMergeR
|
|
31
|
+
extend self
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Merge harvested records into the dataset the fetcher writes to.
|
|
35
|
+
#
|
|
36
|
+
# @param items [Array<Relaton::Itu::ItemData>] as DataCrawlerR#harvest returns
|
|
37
|
+
# @param fetcher [Relaton::Itu::DataFetcher] supplies output_file/write_file
|
|
38
|
+
# and the index, so harvested records get the same pubid guard and
|
|
39
|
+
# unparseable-id reporting as the ITU-T harvest
|
|
40
|
+
#
|
|
41
|
+
# @return [Hash] { added:, backfilled:, unchanged:, skipped:,
|
|
42
|
+
# collisions: [[file, published_doctype, harvested_doctype], …] }
|
|
43
|
+
#
|
|
44
|
+
def write_all(items, fetcher)
|
|
45
|
+
stats = { added: 0, backfilled: 0, unchanged: 0, skipped: 0, collisions: [] }
|
|
46
|
+
seen = {}
|
|
47
|
+
items.each do |bib|
|
|
48
|
+
id = primary_id(bib)
|
|
49
|
+
unless id
|
|
50
|
+
stats[:skipped] += 1
|
|
51
|
+
Util.error "ITU-R merge: record with no primary docidentifier skipped"
|
|
52
|
+
next
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
file = fetcher.output_file id
|
|
56
|
+
# First writer of a filename keeps it; a later claimant is reported and
|
|
57
|
+
# dropped, never merged into a document it isn't.
|
|
58
|
+
if (other = seen[file])
|
|
59
|
+
record_collision stats, file, other, "#{id} (#{doctype bib})", "this harvest"
|
|
60
|
+
next
|
|
61
|
+
end
|
|
62
|
+
seen[file] = "#{id} (#{doctype bib})"
|
|
63
|
+
merge_one bib, id, file, fetcher, stats
|
|
64
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
|
65
|
+
# One unreadable published file must not discard an hour of crawling.
|
|
66
|
+
stats[:skipped] += 1
|
|
67
|
+
Util.error "ITU-R merge: #{id || '(no id)'} skipped: #{e.message}"
|
|
68
|
+
end
|
|
69
|
+
stats
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# @return [void]
|
|
75
|
+
def merge_one(bib, id, file, fetcher, stats)
|
|
76
|
+
unless File.exist? file
|
|
77
|
+
fetcher.write_file bib
|
|
78
|
+
stats[:added] += 1
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
published = Item.from_yaml File.read(file, encoding: "UTF-8")
|
|
83
|
+
published_id = primary_id published
|
|
84
|
+
# Both halves matter: a different doctype means a report and a
|
|
85
|
+
# recommendation share a filename, and a different id means two docids
|
|
86
|
+
# sanitize to one filename (`output_file` collapses `.`, `/` and spaces
|
|
87
|
+
# alike, so `ITU-R BO.4/BL/4` and `ITU-R BO.4-BL-4` would meet here).
|
|
88
|
+
if doctype(published) != doctype(bib) || published_id != id
|
|
89
|
+
record_collision stats, file, "#{published_id} (#{doctype published})",
|
|
90
|
+
"#{id} (#{doctype bib})", "the published dataset"
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if backfill published, bib
|
|
95
|
+
fetcher.write_file published
|
|
96
|
+
stats[:backfilled] += 1
|
|
97
|
+
else
|
|
98
|
+
# Not rewritten: an identical re-serialization would churn the data
|
|
99
|
+
# repo's diff. Index it anyway — the index is rebuilt from scratch on
|
|
100
|
+
# every run, so a record that is not written still has to be in it.
|
|
101
|
+
# Indexed by the id the *file* carries, as #index_files does.
|
|
102
|
+
fetcher.index_primary published_id, file
|
|
103
|
+
stats[:unchanged] += 1
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Fill only what the published record is missing. `date` is deliberately
|
|
108
|
+
# absent from this list — see the module comment.
|
|
109
|
+
#
|
|
110
|
+
# @param published [Relaton::Itu::Item] mutated in place
|
|
111
|
+
# @param bib [Relaton::Itu::ItemData] the harvested record
|
|
112
|
+
# @return [Boolean] whether anything was filled in
|
|
113
|
+
def backfill(published, bib)
|
|
114
|
+
filled = false
|
|
115
|
+
if published.source.nil? || published.source.empty?
|
|
116
|
+
published.source = bib.source
|
|
117
|
+
filled ||= !(bib.source.nil? || bib.source.empty?)
|
|
118
|
+
end
|
|
119
|
+
if published.title.nil? || published.title.empty?
|
|
120
|
+
published.title = bib.title
|
|
121
|
+
filled ||= !(bib.title.nil? || bib.title.empty?)
|
|
122
|
+
end
|
|
123
|
+
filled
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @return [void]
|
|
127
|
+
def record_collision(stats, file, kept, dropped, where)
|
|
128
|
+
stats[:skipped] += 1
|
|
129
|
+
stats[:collisions] << [file, kept, dropped]
|
|
130
|
+
Util.error "ITU-R merge: filename collision on #{file} — #{kept} in #{where} vs " \
|
|
131
|
+
"harvested #{dropped}; not overwritten"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Strictly the primary docid: DataFetcher#write_file reads
|
|
135
|
+
# `docidentifier.find(&:primary).content` with no fallback, so a record
|
|
136
|
+
# without one has nowhere to be written and is skipped here instead.
|
|
137
|
+
#
|
|
138
|
+
# @return [String, nil]
|
|
139
|
+
def primary_id(bib)
|
|
140
|
+
bib.docidentifier.find(&:primary)&.content
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @return [String, nil]
|
|
144
|
+
def doctype(bib)
|
|
145
|
+
bib.ext&.doctype&.content
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -1,140 +1,214 @@
|
|
|
1
1
|
module Relaton
|
|
2
2
|
module Itu
|
|
3
|
+
# Map one normalized ITU-R row to an ItemData.
|
|
4
|
+
#
|
|
5
|
+
# The row comes from DataCrawlerR's three-level `/pub` + `/rec` crawl (issue
|
|
6
|
+
# #75), which replaced the decommissioned RunSearch result hash this module
|
|
7
|
+
# used to consume. The split mirrors DataParserT: the crawler owns the HTTP
|
|
8
|
+
# and the page scraping, this module owns the mapping, so it stays a pure
|
|
9
|
+
# unit with no network in its specs. Row keys:
|
|
10
|
+
#
|
|
11
|
+
# :id "R-REC-BO.1130-5-202602-I" page id
|
|
12
|
+
# :code "BO.1130-5 (02/2026)" displayed code -> primary docid
|
|
13
|
+
# :title English title
|
|
14
|
+
# :status "In force (Main)" | "Superseded" | … (scraped, not modelled)
|
|
15
|
+
# :date "2026-02-18" | "2026-02" approval date for a Recommendation,
|
|
16
|
+
# publication date for a Report
|
|
17
|
+
# :url the record's landing page
|
|
18
|
+
# :pdf absolute dms_pubrec/dms_pub URL
|
|
19
|
+
# :family "R-REC" | "R-REP" | "R-QUE" | "R-RES" | "R-HDB"
|
|
3
20
|
module DataParserR
|
|
4
21
|
extend self
|
|
5
22
|
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
23
|
+
FAMILY_DOCTYPE = {
|
|
24
|
+
"R-REC" => "recommendation",
|
|
25
|
+
"R-REP" => "technical-report",
|
|
26
|
+
"R-QUE" => "question",
|
|
27
|
+
"R-RES" => "resolution",
|
|
28
|
+
"R-HDB" => "handbook",
|
|
12
29
|
}.freeze
|
|
13
30
|
|
|
14
31
|
#
|
|
15
|
-
# Parse ITU-R document from
|
|
32
|
+
# Parse an ITU-R document from a normalized crawler row.
|
|
16
33
|
#
|
|
17
|
-
# @param
|
|
34
|
+
# @param row [Hash] see the key list above
|
|
35
|
+
# @param errors [Hash] shared error tally, `&&=`-narrowed per field so a
|
|
36
|
+
# field that succeeded once is never reported as missing
|
|
18
37
|
#
|
|
19
|
-
# @return [Relaton::Itu::ItemData]
|
|
38
|
+
# @return [Relaton::Itu::ItemData, nil] nil when the family is unknown
|
|
20
39
|
#
|
|
21
|
-
def parse(
|
|
22
|
-
|
|
23
|
-
doctype = fetch_doctype(result)
|
|
40
|
+
def parse(row, errors = {})
|
|
41
|
+
doctype = fetch_doctype(row, errors)
|
|
24
42
|
return unless doctype
|
|
25
43
|
|
|
44
|
+
docid = fetch_docid(row, errors)
|
|
45
|
+
# A record with no primary docid can't be written or indexed —
|
|
46
|
+
# DataFetcher#write_file reads `docidentifier.find(&:primary).content` —
|
|
47
|
+
# so drop it here, as DataParserT does.
|
|
48
|
+
return if docid.empty?
|
|
49
|
+
|
|
26
50
|
Relaton::Itu::ItemData.new(
|
|
27
|
-
docidentifier:
|
|
28
|
-
date: fetch_date(
|
|
29
|
-
source: fetch_source(
|
|
51
|
+
docidentifier: docid, title: fetch_title(row, errors),
|
|
52
|
+
date: fetch_date(row, errors), language: ["en"],
|
|
53
|
+
source: fetch_source(row, errors), script: ["Latn"],
|
|
30
54
|
type: "standard", ext: Relaton::Itu::Ext.new(doctype: doctype, flavor: "itu"),
|
|
31
55
|
)
|
|
32
56
|
end
|
|
33
57
|
|
|
34
|
-
#
|
|
58
|
+
# Each ITU-R family spells its identifier differently, and the published
|
|
59
|
+
# dataset is the authority on which spelling. Verified against
|
|
60
|
+
# relaton-data-itu:
|
|
61
|
+
#
|
|
62
|
+
# R-REC the displayed code "BO.1130-5 (02/2026)" -> ITU-R BO.1130-5
|
|
63
|
+
# R-REP the same, prefixed "BO.1227-2 (1998)" -> Report ITU-R BO.1227-2
|
|
64
|
+
# R-QUE the same, + a colon "202-2/1" -> ITU-R 202-2/1:
|
|
65
|
+
# R-RES the **page id** R-RES-R.1-9-2023 -> ITU-R R.1-9
|
|
66
|
+
# R-HDB the **page id** R-HDB-43-2013 -> ITU-R 43.HDB (2013)
|
|
67
|
+
#
|
|
68
|
+
# Three of those need saying out loud. A **Report** is prefixed because
|
|
69
|
+
# Recommendations and Reports number independently, so `ITU-R BT.2020-1`
|
|
70
|
+
# alone names two different documents (pubid #327 types the prefixed form
|
|
71
|
+
# `pubid:itu:report`, and `output_file` sends it to `report-itu-r-*.yaml`
|
|
72
|
+
# instead of colliding). A **Resolution** cannot use its displayed code at
|
|
73
|
+
# all: the page renders "Res.1-9 (2023)" while the published record is
|
|
74
|
+
# `ITU-R R.1-9`, which only the id carries. A **Handbook** likewise: the
|
|
75
|
+
# index lists its *title* and the leaf shows only a year — see
|
|
76
|
+
# #handbook_docid.
|
|
77
|
+
#
|
|
78
|
+
# The code is NBSP-folded first — the cells are padded with ` `, which
|
|
79
|
+
# neither `\s` nor String#strip match, and an NBSP left in a docid
|
|
80
|
+
# sanitizes into the filename and defeats `Pubid::Itu`.
|
|
81
|
+
#
|
|
82
|
+
# @param row [Hash]
|
|
83
|
+
# @return [String, nil] nil when the row carries nothing to build from
|
|
84
|
+
def family_docid(row)
|
|
85
|
+
# " " spelled as an escape on purpose: a literal NBSP here is invisible
|
|
86
|
+
# and has been silently lost by tooling before.
|
|
87
|
+
#
|
|
88
|
+
# The date suffix is cut at the bare `(`, leaving the space before it to
|
|
89
|
+
# #strip. A leading `\s*` would make this polynomial-time (CodeQL
|
|
90
|
+
# rb/polynomial-redos): unanchored, it can start matching anywhere in a
|
|
91
|
+
# run of spaces, so a code that never reaches a `(` costs one pass per
|
|
92
|
+
# space. Same result, no ambiguity.
|
|
93
|
+
code = row[:code].to_s.tr(" ", " ").sub(/\(.*\z/m, "").strip
|
|
94
|
+
# The two id-derived families, before `code` gets a say.
|
|
95
|
+
return "ITU-R #{resolution_number row}" if row[:family] == "R-RES"
|
|
96
|
+
return handbook_docid(row) if row[:family] == "R-HDB"
|
|
97
|
+
return nil if code.empty?
|
|
98
|
+
|
|
99
|
+
case row[:family]
|
|
100
|
+
when "R-REP" then "Report ITU-R #{code}"
|
|
101
|
+
when "R-QUE" then "ITU-R #{code}:"
|
|
102
|
+
else "ITU-R #{code}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# "R-HDB-43-2013" -> "ITU-R 43.HDB (2013)".
|
|
107
|
+
#
|
|
108
|
+
# The displayed code is unusable twice over: the index cell holds the
|
|
109
|
+
# handbook's *title*, and the leaf holds a year that can disagree with its
|
|
110
|
+
# own id (`R-HDB-43-2013` displays "2014"). The id is the only consistent
|
|
111
|
+
# source, so the year comes from there.
|
|
112
|
+
#
|
|
113
|
+
# Keyed **per edition** because ITU publishes several per handbook (43 has
|
|
114
|
+
# 2002, 2013 and 2026) and `Pubid::Itu` models the year, so they stay
|
|
115
|
+
# distinct records. The bare `ITU-R 43` that four published records carry
|
|
116
|
+
# parses as a *recommendation*, claiming a number that is not its own —
|
|
117
|
+
# this is what replaces it.
|
|
118
|
+
#
|
|
119
|
+
# @param row [Hash]
|
|
120
|
+
# @return [String, nil]
|
|
121
|
+
def handbook_docid(row)
|
|
122
|
+
m = row[:id].to_s.match(/\AR-HDB-(?<number>\d+)(?:-(?<year>(?:19|20)\d{2}))?/)
|
|
123
|
+
return nil unless m
|
|
124
|
+
|
|
125
|
+
m[:year] ? "ITU-R #{m[:number]}.HDB (#{m[:year]})" : "ITU-R #{m[:number]}.HDB"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# "R-RES-R.1-9-2023" -> "R.1-9"
|
|
129
|
+
#
|
|
130
|
+
# @param row [Hash]
|
|
131
|
+
# @return [String, nil]
|
|
132
|
+
def resolution_number(row)
|
|
133
|
+
n = row[:id].to_s.sub(/\AR-RES-/, "").sub(/-(?:19|20)\d{2}\z/, "")
|
|
134
|
+
n.empty? ? nil : n
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# @param row [Hash]
|
|
35
138
|
# @return [Array<Relaton::Bib::Docidentifier>]
|
|
36
|
-
def fetch_docid(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
139
|
+
def fetch_docid(row, errors = {})
|
|
140
|
+
content = family_docid row
|
|
141
|
+
if content.nil? || content == "ITU-R "
|
|
142
|
+
errors[:docid] &&= true
|
|
143
|
+
return []
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
r = [Docidentifier.new(type: "ITU", content: content, primary: true)]
|
|
147
|
+
errors[:docid] &&= r.empty?
|
|
148
|
+
r
|
|
45
149
|
end
|
|
46
150
|
|
|
47
|
-
# @param
|
|
151
|
+
# @param row [Hash]
|
|
48
152
|
# @return [Array<Relaton::Bib::Title>]
|
|
49
|
-
def fetch_title(
|
|
50
|
-
|
|
51
|
-
content
|
|
52
|
-
|
|
153
|
+
def fetch_title(row, errors = {})
|
|
154
|
+
content = row[:title].to_s.strip
|
|
155
|
+
if content.empty?
|
|
156
|
+
errors[:title] &&= true
|
|
157
|
+
return []
|
|
158
|
+
end
|
|
159
|
+
|
|
53
160
|
r = [Relaton::Bib::Title.new(type: "main", content: content, language: "en", script: "Latn")]
|
|
54
|
-
|
|
161
|
+
errors[:title] &&= r.empty?
|
|
55
162
|
r
|
|
56
163
|
end
|
|
57
164
|
|
|
58
|
-
#
|
|
165
|
+
# Whatever date the crawler could see: for a **Recommendation** that is the
|
|
166
|
+
# approval date — *not* the publication date the preserved records carry,
|
|
167
|
+
# which died with RunSearch (issue #75), which is why DataMergeR never
|
|
168
|
+
# rewrites one — and for a **Report** it is the publication date itself,
|
|
169
|
+
# read off the edition page's posted files. Day precision when it comes
|
|
170
|
+
# from the page, month or year precision when derived from the page id.
|
|
171
|
+
#
|
|
172
|
+
# @param row [Hash]
|
|
59
173
|
# @return [Array<Relaton::Bib::Date>]
|
|
60
|
-
def fetch_date(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return []
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
date = parse_pub_date(prop)
|
|
68
|
-
unless date
|
|
69
|
-
@errors[:date] &&= true
|
|
174
|
+
def fetch_date(row, errors = {})
|
|
175
|
+
date = row[:date].to_s.strip
|
|
176
|
+
if date.empty?
|
|
177
|
+
errors[:date] &&= true
|
|
70
178
|
return []
|
|
71
179
|
end
|
|
72
180
|
|
|
73
181
|
r = [Relaton::Bib::Date.new(type: "published", at: date)]
|
|
74
|
-
|
|
182
|
+
errors[:date] &&= r.empty?
|
|
75
183
|
r
|
|
76
184
|
end
|
|
77
185
|
|
|
78
|
-
# @param
|
|
186
|
+
# @param row [Hash]
|
|
79
187
|
# @return [Array<Relaton::Bib::Uri>]
|
|
80
|
-
def fetch_source(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
188
|
+
def fetch_source(row, errors = {})
|
|
189
|
+
pdf = row[:pdf].to_s.strip
|
|
190
|
+
if pdf.empty?
|
|
191
|
+
errors[:source] &&= true
|
|
84
192
|
return []
|
|
85
193
|
end
|
|
86
194
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@errors[:source] &&= true
|
|
90
|
-
return []
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
r = [Relaton::Bib::Uri.new(type: "pdf", content: pdf["RawHref"])]
|
|
94
|
-
@errors[:source] &&= r.empty?
|
|
195
|
+
r = [Relaton::Bib::Uri.new(type: "pdf", content: pdf)]
|
|
196
|
+
errors[:source] &&= r.empty?
|
|
95
197
|
r
|
|
96
198
|
end
|
|
97
199
|
|
|
98
|
-
# @param
|
|
200
|
+
# @param row [Hash]
|
|
99
201
|
# @return [Relaton::Itu::Doctype, nil]
|
|
100
|
-
def fetch_doctype(
|
|
101
|
-
|
|
102
|
-
mapped = TYPE_MAP[type_value]
|
|
202
|
+
def fetch_doctype(row, errors = {})
|
|
203
|
+
mapped = FAMILY_DOCTYPE[row[:family]]
|
|
103
204
|
unless mapped
|
|
104
|
-
|
|
205
|
+
errors[:doctype] &&= true
|
|
105
206
|
return
|
|
106
207
|
end
|
|
107
208
|
|
|
108
|
-
|
|
209
|
+
errors[:doctype] &&= false
|
|
109
210
|
Doctype.new(content: mapped)
|
|
110
211
|
end
|
|
111
|
-
|
|
112
|
-
private
|
|
113
|
-
|
|
114
|
-
# Find a property value from the result's Properties array.
|
|
115
|
-
# @param result [Hash]
|
|
116
|
-
# @param name [String]
|
|
117
|
-
# @return [String, nil]
|
|
118
|
-
def property(result, name)
|
|
119
|
-
props = result["Properties"]
|
|
120
|
-
return unless props.is_a?(Array)
|
|
121
|
-
|
|
122
|
-
entry = props.find { |p| p["Title"] == name }
|
|
123
|
-
entry&.[]("Value")
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# Parse publication date string like "January, 2024" or "2024".
|
|
127
|
-
# @param value [String]
|
|
128
|
-
# @return [String, nil]
|
|
129
|
-
def parse_pub_date(value)
|
|
130
|
-
case value
|
|
131
|
-
when /(\w+),?\s+(\d{4})/
|
|
132
|
-
month = Date::MONTHNAMES.index($1)
|
|
133
|
-
month ? "#{$2}-#{format('%02d', month)}" : $2
|
|
134
|
-
when /(\d{4})/
|
|
135
|
-
$1
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
212
|
end
|
|
139
213
|
end
|
|
140
214
|
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
require_relative "../itu"
|
|
2
|
+
require_relative "recommendation_parser"
|
|
3
|
+
|
|
4
|
+
module Relaton
|
|
5
|
+
module Itu
|
|
6
|
+
# Parse a single row of the ITU-T recommendation index returned by the
|
|
7
|
+
# mws/api/recommendations/searchRecs endpoint (see DataFetcher#search_recs)
|
|
8
|
+
# into an ItemData. Recommendations and supplements share one row schema and
|
|
9
|
+
# are distinguished by "Suppl" in rec_name; each edition is its own row, so
|
|
10
|
+
# the docid keeps the (MM/YYYY) edition date (e.g. "ITU-T A.1 (10/2000)") —
|
|
11
|
+
# which Pubid::Itu parses per edition, keeping index rows and filenames
|
|
12
|
+
# unique across editions. Mirrors DataParserR (the ITU-R row parser).
|
|
13
|
+
#
|
|
14
|
+
# The searchRecs row is metadata-thin (docid/title/date/source/doctype). When
|
|
15
|
+
# an `agent` is given, each record is **enriched** with the same
|
|
16
|
+
# `getRecHdrDetail`-sourced fields the live runtime path emits — abstract,
|
|
17
|
+
# ISO/IEC co-identifier, editorial-group contributors, status — via the shared
|
|
18
|
+
# `RecommendationFields` (through `RecommendationParser`), so the harvested
|
|
19
|
+
# record matches a live `Bibliography.get`. Enrichment is best-effort: a
|
|
20
|
+
# detail-fetch failure degrades to the thin record rather than losing it.
|
|
21
|
+
module DataParserT
|
|
22
|
+
extend self
|
|
23
|
+
|
|
24
|
+
# rec_name markers → Doctype. Ordered; first match wins. A plain edition
|
|
25
|
+
# (no marker) is a "recommendation". Values are members of Doctype::TYPES.
|
|
26
|
+
DOCTYPE_MARKERS = {
|
|
27
|
+
/\bSuppl\b/ => "recommendation-supplement",
|
|
28
|
+
/\bAmd\b/ => "recommendation-amendment",
|
|
29
|
+
/\bCor\b/ => "recommendation-corrigendum",
|
|
30
|
+
/\bAnnex\b/ => "recommendation-annex",
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
# The `errors` hash is threaded through the fetch_* helpers rather than
|
|
34
|
+
# held in an ivar: this module is `extend self`, so an ivar would be state
|
|
35
|
+
# shared by every caller — and DataFetcher#fetch_recommendations parses
|
|
36
|
+
# rows from a pool of threads.
|
|
37
|
+
#
|
|
38
|
+
# @param row [Hash] single row from searchRecs Data
|
|
39
|
+
# @param agent [Mechanize, nil] when present, enrich via getRecHdrDetail
|
|
40
|
+
# @param errors [Hash]
|
|
41
|
+
# @return [Relaton::Itu::ItemData, nil]
|
|
42
|
+
def parse(row, agent = nil, errors = {}, cache: NullCache.instance)
|
|
43
|
+
docid = fetch_docid(row, errors)
|
|
44
|
+
return if docid.empty?
|
|
45
|
+
|
|
46
|
+
enr = enrichment(row, agent, cache)
|
|
47
|
+
date = fetch_date(row, errors)
|
|
48
|
+
row_source = fetch_source(row, errors)
|
|
49
|
+
Relaton::Itu::ItemData.new(
|
|
50
|
+
docidentifier: docid + enr.fetch(:iso, []),
|
|
51
|
+
title: fetch_title(row, errors),
|
|
52
|
+
edition: enr[:edition],
|
|
53
|
+
abstract: enr.fetch(:abstract, []),
|
|
54
|
+
date: date, language: ["en"],
|
|
55
|
+
status: enr[:status],
|
|
56
|
+
relation: enr.fetch(:relation, []),
|
|
57
|
+
contributor: enr.fetch(:contributor, []),
|
|
58
|
+
copyright: fetch_copyright(date),
|
|
59
|
+
place: [Relaton::Bib::Place.new(city: "Geneva")],
|
|
60
|
+
source: enr[:source] || row_source, script: ["Latn"],
|
|
61
|
+
type: "standard",
|
|
62
|
+
ext: Relaton::Itu::Ext.new(doctype: fetch_doctype(row, errors), flavor: "itu"),
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Best-effort getRecHdrDetail enrichment for one record. Returns {} when
|
|
67
|
+
# there is no agent or the detail fetch fails, so the record still gets its
|
|
68
|
+
# thin fields. ITU-T records are always sector T → abbreviation "ITU",
|
|
69
|
+
# bureau "T".
|
|
70
|
+
#
|
|
71
|
+
# @param row [Hash]
|
|
72
|
+
# @param agent [Mechanize, nil]
|
|
73
|
+
# @param cache [#fetch, #warm] shared across the worker pool, so the
|
|
74
|
+
# family-invariant endpoints are fetched once per recommendation rather
|
|
75
|
+
# than once per edition. Like `errors`, it is threaded through as a
|
|
76
|
+
# parameter rather than held as state — DataParserT is `extend self`.
|
|
77
|
+
# The source is the handle URI and the PDF URI, as on the live path. It
|
|
78
|
+
# is nil when the header has no handle_id, so the row source is used.
|
|
79
|
+
# @return [Hash] { iso:, abstract:, status:, edition:, relation:,
|
|
80
|
+
# contributor:, source: }
|
|
81
|
+
def enrichment(row, agent, cache = NullCache.instance)
|
|
82
|
+
return {} unless agent && row["idrec"]
|
|
83
|
+
|
|
84
|
+
f = RecommendationParser.new(agent, row["idrec"], false, cache: cache)
|
|
85
|
+
ed = f.fetch_edition
|
|
86
|
+
{
|
|
87
|
+
iso: Array(f.iso_docid),
|
|
88
|
+
abstract: f.fetch_abstract,
|
|
89
|
+
status: f.fetch_status,
|
|
90
|
+
edition: (Relaton::Bib::Edition.new(content: ed) if ed),
|
|
91
|
+
relation: f.fetch_relations,
|
|
92
|
+
contributor: [f.publisher("ITU"), f.editorial_group("T")].compact,
|
|
93
|
+
source: (f.fetch_source if f.doc["handle_id"]),
|
|
94
|
+
}
|
|
95
|
+
rescue StandardError => e
|
|
96
|
+
Util.warn "ITU-T enrichment failed for idrec=#{row['idrec']}: #{e.message}"
|
|
97
|
+
{}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The ITU copyright, from the record's own publication year — no detail
|
|
101
|
+
# fetch needed, so a thin (un-enriched) record carries it too. Mirrors
|
|
102
|
+
# `Scraper#fetch_copyright` on the live path.
|
|
103
|
+
#
|
|
104
|
+
# @param date [Array<Relaton::Bib::Date>] the record's dates
|
|
105
|
+
# @return [Array<Relaton::Bib::Copyright>]
|
|
106
|
+
def fetch_copyright(date)
|
|
107
|
+
year = date.first&.at.to_s[/\d{4}/]
|
|
108
|
+
return [] unless year
|
|
109
|
+
|
|
110
|
+
org = Relaton::Bib::Organization.new(
|
|
111
|
+
name: [Relaton::Bib::TypedLocalizedString.new(content: "International Telecommunication Union")],
|
|
112
|
+
abbreviation: Relaton::Bib::LocalizedString.new(content: "ITU"),
|
|
113
|
+
uri: [Relaton::Bib::Uri.new(content: "www.itu.int")],
|
|
114
|
+
)
|
|
115
|
+
owner = [Relaton::Bib::ContributionInfo.new(organization: org)]
|
|
116
|
+
[Relaton::Bib::Copyright.new(from: year, owner: owner)]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The primary docid is the rec_name prefixed with "ITU-T", keeping the
|
|
120
|
+
# trailing " (MM/YYYY)" edition date so Pubid::Itu identifies each edition
|
|
121
|
+
# distinctly (matches the existing "ITU-T L.163 (11/2018)" convention).
|
|
122
|
+
#
|
|
123
|
+
# @param row [Hash]
|
|
124
|
+
# @return [Array<Relaton::Itu::Docidentifier>]
|
|
125
|
+
def fetch_docid(row, errors = {})
|
|
126
|
+
name = normalize_rec_name row["rec_name"]
|
|
127
|
+
if name.empty?
|
|
128
|
+
errors[:docid] &&= true
|
|
129
|
+
return []
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
r = [Docidentifier.new(type: "ITU", content: "ITU-T #{name}", primary: true)]
|
|
133
|
+
errors[:docid] &&= r.empty?
|
|
134
|
+
r
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# One rule, for the one spelling `::Pubid::Itu` deliberately will not take:
|
|
138
|
+
# a space where the series dot belongs ("G 231 (10/1976)"). pubid rejects it
|
|
139
|
+
# on purpose, because a space there is ambiguous against the series-only
|
|
140
|
+
# supplement form ("G Suppl. 1"), so relaton canonicalises it rather than
|
|
141
|
+
# leaving 4 records unindexed. `#index_primary` drops ids pubid rejects, and
|
|
142
|
+
# a dropped record is written but never indexed — reachable only through the
|
|
143
|
+
# live `rec.aspx` fallback.
|
|
144
|
+
#
|
|
145
|
+
# This changes **no filename**: `Core::DataFetcher#output_file` collapses
|
|
146
|
+
# runs of whitespace and punctuation alike, so `"G 231 (10/1976)"` and
|
|
147
|
+
# `"G.231 (10/1976)"` both yield `itu-t-g-231-10-1976.yaml`. The affected
|
|
148
|
+
# records simply gain an index row.
|
|
149
|
+
#
|
|
150
|
+
# Everything else searchRecs spells oddly — the doubled space, `Suppl.1` run
|
|
151
|
+
# into its number, and bare `v10`/`V2`/`v.1` versions — **parses natively**
|
|
152
|
+
# as of pubid #325, so the rules that used to rewrite those are gone: pubid
|
|
153
|
+
# now canonicalises on `to_s` itself and ITU's own spelling is kept in the
|
|
154
|
+
# docid. Forms pubid cannot parse are still left alone; guessing a spelling
|
|
155
|
+
# would invent identifiers rather than canonicalise them.
|
|
156
|
+
#
|
|
157
|
+
# @param rec_name [String, nil]
|
|
158
|
+
# @return [String]
|
|
159
|
+
def normalize_rec_name(rec_name)
|
|
160
|
+
rec_name.to_s.strip.sub(/\A([A-Z]+) (\d)/, '\1.\2')
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# @param row [Hash]
|
|
164
|
+
# @return [Array<Relaton::Bib::Title>]
|
|
165
|
+
def fetch_title(row, errors = {})
|
|
166
|
+
content = row["title"].to_s.strip
|
|
167
|
+
r = content.empty? ? [] : [Relaton::Bib::Title.new(type: "main", content: content, language: "en", script: "Latn")]
|
|
168
|
+
errors[:title] &&= r.empty?
|
|
169
|
+
r
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Prefer a full day-precision approval_date (YYYY-MM-DD, matching the live
|
|
173
|
+
# runtime path); fall back to the (MM/YYYY) edition date in rec_name; and
|
|
174
|
+
# only then to a coarser (year / year-month) approval_date. Ordering this way
|
|
175
|
+
# means a coarse or malformed approval_date never downgrades precision below
|
|
176
|
+
# the always-well-formed rec_name edition date.
|
|
177
|
+
#
|
|
178
|
+
# @param row [Hash]
|
|
179
|
+
# @return [Array<Relaton::Bib::Date>]
|
|
180
|
+
def fetch_date(row, errors = {})
|
|
181
|
+
approval = row["approval_date"].to_s
|
|
182
|
+
date = approval[/\d{4}-\d{2}-\d{2}/] || rec_name_date(row["rec_name"]) || approval[/\d{4}(-\d{2})?/]
|
|
183
|
+
if date.nil? || date.empty?
|
|
184
|
+
errors[:date] &&= true
|
|
185
|
+
return []
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
r = [Relaton::Bib::Date.new(type: "published", at: date)]
|
|
189
|
+
errors[:date] &&= r.empty?
|
|
190
|
+
r
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# @param row [Hash]
|
|
194
|
+
# @return [Array<Relaton::Bib::Uri>]
|
|
195
|
+
def fetch_source(row, errors = {})
|
|
196
|
+
link = row["dms_link"].to_s.strip
|
|
197
|
+
if link.empty? || link == "-"
|
|
198
|
+
errors[:source] &&= true
|
|
199
|
+
return []
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
r = [Relaton::Bib::Uri.new(type: "src", content: link)]
|
|
203
|
+
errors[:source] &&= r.empty?
|
|
204
|
+
r
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# @param row [Hash]
|
|
208
|
+
# @return [Relaton::Itu::Doctype]
|
|
209
|
+
def fetch_doctype(row, errors = {})
|
|
210
|
+
name = row["rec_name"].to_s
|
|
211
|
+
content = DOCTYPE_MARKERS.find { |re, _| name.match?(re) }&.last || "recommendation"
|
|
212
|
+
errors[:doctype] &&= false
|
|
213
|
+
Doctype.new(content: content)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
private
|
|
217
|
+
|
|
218
|
+
# "A.1 (10/2000)" -> "2000-10"
|
|
219
|
+
# @param name [String, nil]
|
|
220
|
+
# @return [String, nil]
|
|
221
|
+
def rec_name_date(name)
|
|
222
|
+
return unless name =~ %r{\((\d{2})/(\d{4})\)}
|
|
223
|
+
|
|
224
|
+
"#{$2}-#{$1}"
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|