relaton 3.0.0 → 3.0.1
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/3gpp/CLAUDE.md +31 -0
- data/lib/relaton/3gpp/bibliography.rb +1 -1
- data/lib/relaton/3gpp/data_fetcher.rb +32 -9
- data/lib/relaton/3gpp/date.rb +13 -0
- data/lib/relaton/3gpp/item.rb +3 -0
- data/lib/relaton/3gpp/parser.rb +9 -6
- data/lib/relaton/bib/model/item_shared.rb +12 -1
- data/lib/relaton/calconnect/data_fetcher.rb +9 -1
- data/lib/relaton/cie/authors_parser.rb +78 -0
- data/lib/relaton/cie/data_fetcher.rb +23 -18
- data/lib/relaton/cie.rb +1 -0
- data/lib/relaton/core/data_fetcher.rb +21 -0
- data/lib/relaton/iana/data_fetcher.rb +53 -4
- data/lib/relaton/ieee/CLAUDE.md +30 -4
- data/lib/relaton/ieee/bibliography.rb +87 -32
- data/lib/relaton/ieee/data_fetcher.rb +1 -1
- data/lib/relaton/ieee/docidentifier.rb +37 -0
- data/lib/relaton/ieee/idams_parser.rb +2 -2
- data/lib/relaton/ieee/item.rb +3 -0
- data/lib/relaton/ieee/item_data.rb +7 -1
- data/lib/relaton/jis/hit_collection.rb +1 -2
- data/lib/relaton/jis.rb +1 -0
- data/lib/relaton/nist/hit_collection.rb +1 -1
- data/lib/relaton/nist.rb +1 -0
- data/lib/relaton/plateau/hit_collection.rb +1 -1
- data/lib/relaton/plateau.rb +1 -0
- data/lib/relaton/version.rb +1 -1
- metadata +25 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f81699e1df94f1c570f01c07b4114492b7cc5ae1e45715a4ced3ef51c1ba3fb6
|
|
4
|
+
data.tar.gz: 1ea38d455d16b512807a505bd558757ea0509a9392cf41c120f3fc10801beb87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d27294461f66031023053e1ec70a4c26b0c8fa068e6ed9d1224d1ab9c6cb366a2b2d8f85cd50305c4826733ab028817f70b96aebc267346d224b9be1fdf29be
|
|
7
|
+
data.tar.gz: a3c00ef5a93352f08a492624b531955494c8530a6b0d68f01dc748d58d8599304fa9ac743d157cfd2a6cc37b63664c781da75de5e199de70035ed133e70d0305
|
data/lib/relaton/3gpp/CLAUDE.md
CHANGED
|
@@ -154,6 +154,37 @@ guards does not depend on it. `Processor` also sets
|
|
|
154
154
|
`@pubid_flavor = :Tgpp`, so `Core::Processor#prefixes` reads
|
|
155
155
|
`Pubid::Tgpp.prefixes` (`["3GPP"]`).
|
|
156
156
|
|
|
157
|
+
### Dates: published, then the Release start and end
|
|
158
|
+
|
|
159
|
+
`Parser#parse_date` emits the `published` date (CSV `Date`) first, then
|
|
160
|
+
`project-start` from the CSV `Project Start` column and `released` from
|
|
161
|
+
`Project End` (relaton-3gpp#13). #15 defines the Release end date as the date
|
|
162
|
+
the Release is released, and the issue thread asks for it as the release date
|
|
163
|
+
of the specification — hence `released`, not `project-end`. Both are the dates
|
|
164
|
+
of the document's **Release**, not of the document: every version in one
|
|
165
|
+
Release carries the same pair, so neither fits a standard type (`issued` would
|
|
166
|
+
date a 2004 `REL-98` version to 1999). They also stay in `ext/release`
|
|
167
|
+
(`project_start` / `project_end`). An empty column gives no date;
|
|
168
|
+
`@errors[:date]` tracks the published date only.
|
|
169
|
+
|
|
170
|
+
- **`Relaton::ThreeGpp::Date`** (`date.rb`) extends `Bib::Date`'s `type`
|
|
171
|
+
value list with the two types, and `Item` shadows `date` with it (as it does
|
|
172
|
+
`docidentifier`). Without it `validate!` rejects the types; the RelaxNG
|
|
173
|
+
grammar already accepts any text. The shadow covers the top-level item
|
|
174
|
+
only: an item nested in a `relation` (the transposition) reloads as
|
|
175
|
+
`Bib::ItemBase` with plain `Bib::Date`s. Nothing validates those today.
|
|
176
|
+
- **The constant hides Ruby's `::Date`** inside `Relaton::ThreeGpp`. Write
|
|
177
|
+
`::Date.parse` / `::Date.today` in this flavor; a bare `Date.today` now
|
|
178
|
+
raises `NoMethodError`.
|
|
179
|
+
- **Select the publication date by type, never by `date[0]`.**
|
|
180
|
+
`DataFetcher#published_date` does this for the transposition check: a row
|
|
181
|
+
with no `Date` still has the two Release dates, so `date.any?` no longer
|
|
182
|
+
means "has a publication date".
|
|
183
|
+
- `spec/3gpp/fixtures/bib.xml`, `bibdata.xml` and `bib.yaml` are rendered from
|
|
184
|
+
the `3gpp_get_document` cassette, i.e. from the published
|
|
185
|
+
`relaton-data-3gpp` record. They get the two dates only after that repo
|
|
186
|
+
re-crawls and the cassette is re-recorded — do not hand-add them.
|
|
187
|
+
|
|
157
188
|
## Testing
|
|
158
189
|
|
|
159
190
|
- `bundle exec rake spec:3gpp` — run the 3GPP suite.
|
|
@@ -25,7 +25,7 @@ module Relaton
|
|
|
25
25
|
return unless resp.code == "200"
|
|
26
26
|
|
|
27
27
|
item = Item.from_yaml(resp.body)
|
|
28
|
-
item.fetched = Date.today.to_s
|
|
28
|
+
item.fetched = ::Date.today.to_s
|
|
29
29
|
item
|
|
30
30
|
rescue SocketError, Timeout::Error, Errno::EINVAL, Errno::ECONNRESET,
|
|
31
31
|
EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
|
|
@@ -42,7 +42,14 @@ module Relaton
|
|
|
42
42
|
index.remove_all # if renewal
|
|
43
43
|
end
|
|
44
44
|
CSV.open(file, "r:bom|utf-8", headers: true, col_sep: ";").each do |row|
|
|
45
|
-
|
|
45
|
+
# The row carries the whole record payload, so an unchanged row
|
|
46
|
+
# skips the parse and the write (the digest ledger). The row is
|
|
47
|
+
# only read when the ledger is on.
|
|
48
|
+
key = @digest_ledger ? "#{row[0]}/#{row['Version']}" : nil
|
|
49
|
+
next if key && unchanged?(key, row.to_h)
|
|
50
|
+
|
|
51
|
+
file = save_doc Parser.parse(row, @errors)
|
|
52
|
+
note_digest key, row.to_h, file: file if key
|
|
46
53
|
end
|
|
47
54
|
File.write CURRENT, @current.to_yaml, encoding: "UTF-8"
|
|
48
55
|
index.save
|
|
@@ -132,7 +139,11 @@ module Relaton
|
|
|
132
139
|
@files << file
|
|
133
140
|
index_primary bib1.docidentifier&.detect(&:primary), file
|
|
134
141
|
end
|
|
135
|
-
|
|
142
|
+
return nil if bib1.nil?
|
|
143
|
+
|
|
144
|
+
File.write file, serialize(bib1), encoding: "UTF-8"
|
|
145
|
+
record_written file
|
|
146
|
+
file
|
|
136
147
|
end
|
|
137
148
|
|
|
138
149
|
#
|
|
@@ -210,11 +221,9 @@ module Relaton
|
|
|
210
221
|
# @return [Array<Relaton3gpp::BibliographicItem, Boolean>] main bibliographic item,
|
|
211
222
|
# related bibliographic item, true if relation has been added
|
|
212
223
|
#
|
|
213
|
-
def transposed_relation(bib, existed)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
end
|
|
217
|
-
return [existed, bib, true] if bib.date.none? && existed.date.any?
|
|
224
|
+
def transposed_relation(bib, existed)
|
|
225
|
+
return [bib, existed, false] unless published_date(existed)
|
|
226
|
+
return [existed, bib, true] unless published_date(bib)
|
|
218
227
|
|
|
219
228
|
check_transposed_date bib, existed
|
|
220
229
|
end
|
|
@@ -229,16 +238,30 @@ module Relaton
|
|
|
229
238
|
# related bibliographic item, true if relation has been added
|
|
230
239
|
#
|
|
231
240
|
def check_transposed_date(bib, existed)
|
|
232
|
-
|
|
241
|
+
bib_date = published_date(bib).at
|
|
242
|
+
existed_date = published_date(existed).at
|
|
243
|
+
if bib_date < existed_date
|
|
233
244
|
add_transposed_relation bib, existed
|
|
234
245
|
[bib, existed, true]
|
|
235
|
-
elsif
|
|
246
|
+
elsif bib_date > existed_date
|
|
236
247
|
add_transposed_relation existed, bib
|
|
237
248
|
[existed, bib, true]
|
|
238
249
|
else [bib, existed, false]
|
|
239
250
|
end
|
|
240
251
|
end
|
|
241
252
|
|
|
253
|
+
#
|
|
254
|
+
# The publication date. The date array also holds the Release start and
|
|
255
|
+
# end, which say nothing about which version came first.
|
|
256
|
+
#
|
|
257
|
+
# @param [Relaton::ThreeGpp::ItemData] bib bibliographic item
|
|
258
|
+
#
|
|
259
|
+
# @return [Relaton::ThreeGpp::Date, nil]
|
|
260
|
+
#
|
|
261
|
+
def published_date(bib)
|
|
262
|
+
bib.date.detect { |d| d.type == "published" }
|
|
263
|
+
end
|
|
264
|
+
|
|
242
265
|
#
|
|
243
266
|
# Add transposed relation
|
|
244
267
|
#
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module ThreeGpp
|
|
3
|
+
# A bibliographic date that also accepts the start of the document's 3GPP
|
|
4
|
+
# Release (`project-start`) and its end, which is the release date of the
|
|
5
|
+
# Release (`released`, relaton-3gpp#13). The RelaxNG grammar takes any text
|
|
6
|
+
# as a date type; only the Bib::Date value list needs the two extra types.
|
|
7
|
+
class Date < Bib::Date
|
|
8
|
+
RELEASE_TYPES = %w[project-start released].freeze
|
|
9
|
+
|
|
10
|
+
attribute :type, :string, values: Bib::Date.attributes[:type].options[:values] + RELEASE_TYPES
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/relaton/3gpp/item.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require_relative "item_data"
|
|
2
2
|
require_relative "ext"
|
|
3
3
|
require_relative "docidentifier"
|
|
4
|
+
require_relative "date"
|
|
4
5
|
|
|
5
6
|
module Relaton
|
|
6
7
|
module ThreeGpp
|
|
@@ -13,6 +14,8 @@ module Relaton
|
|
|
13
14
|
# Item, so this one declaration covers all three.
|
|
14
15
|
attribute :docidentifier, Docidentifier, collection: true,
|
|
15
16
|
initialize_empty: true
|
|
17
|
+
# Same shadow for the dates, which carry the Release start and end.
|
|
18
|
+
attribute :date, Date, collection: true, initialize_empty: true
|
|
16
19
|
end
|
|
17
20
|
end
|
|
18
21
|
end
|
data/lib/relaton/3gpp/parser.rb
CHANGED
|
@@ -143,17 +143,20 @@ module Relaton
|
|
|
143
143
|
# end
|
|
144
144
|
|
|
145
145
|
#
|
|
146
|
-
# Parse date
|
|
146
|
+
# Parse date. The published date comes first, then the start of the
|
|
147
|
+
# document's Release and its end as the release date (relaton-3gpp#13).
|
|
147
148
|
#
|
|
148
|
-
# @return [Array<
|
|
149
|
+
# @return [Array<Relaton::ThreeGpp::Date>] date
|
|
149
150
|
#
|
|
150
151
|
def parse_date
|
|
151
152
|
dates = []
|
|
152
153
|
if @row["Date"]
|
|
153
|
-
on = Date.parse(@row["Date"]).to_s
|
|
154
|
-
dates <<
|
|
154
|
+
on = ::Date.parse(@row["Date"]).to_s
|
|
155
|
+
dates << Date.new(type: "published", at: on)
|
|
155
156
|
end
|
|
156
157
|
@errors[:date] &&= dates.empty?
|
|
158
|
+
dates << Date.new(type: "project-start", at: project_start.to_s) if project_start
|
|
159
|
+
dates << Date.new(type: "released", at: project_end.to_s) if project_end
|
|
157
160
|
dates
|
|
158
161
|
end
|
|
159
162
|
|
|
@@ -388,12 +391,12 @@ module Relaton
|
|
|
388
391
|
|
|
389
392
|
def project_start
|
|
390
393
|
@errors[:project_start] &&= @row["Project Start"].nil? || @row["Project Start"].empty?
|
|
391
|
-
Date.parse(@row["Project Start"]) if @row["Project Start"]
|
|
394
|
+
::Date.parse(@row["Project Start"]) if @row["Project Start"]
|
|
392
395
|
end
|
|
393
396
|
|
|
394
397
|
def project_end
|
|
395
398
|
@errors[:project_end] &&= @row["Project End"].nil? || @row["Project End"].empty?
|
|
396
|
-
Date.parse(@row["Project End"]) if @row["Project End"]
|
|
399
|
+
::Date.parse(@row["Project End"]) if @row["Project End"]
|
|
397
400
|
end
|
|
398
401
|
end
|
|
399
402
|
end
|
|
@@ -59,10 +59,21 @@ module Relaton
|
|
|
59
59
|
def self.prune_attribute(base, attr_name, xml_name)
|
|
60
60
|
return unless base.attributes.key?(attr_name)
|
|
61
61
|
|
|
62
|
+
# lutaml-model 0.8.142 shares a subclass's mappings/attributes with
|
|
63
|
+
# its superclass until the class's own DSL forks them (copy-on-write).
|
|
64
|
+
# These deletes must stay private to `base`: without the fork, pruning
|
|
65
|
+
# Bibdata's :id erased it from Bibitem (and Bibitem's :ext from
|
|
66
|
+
# Bibdata), and every from_xml raised "Attribute ... not found".
|
|
67
|
+
# 0.8.141 and older give each class private copies and have no
|
|
68
|
+
# lutaml_cow_fork!, where mutating in place was already safe.
|
|
69
|
+
if base.respond_to?(:lutaml_cow_fork!)
|
|
70
|
+
base.lutaml_cow_fork!(:mappings)
|
|
71
|
+
base.lutaml_cow_fork!(:attributes)
|
|
72
|
+
end
|
|
62
73
|
xml_mapping = base.mappings[:xml]
|
|
63
74
|
xml_mapping.instance_variable_get(:@elements).delete(xml_name)
|
|
64
75
|
xml_mapping.instance_variable_get(:@attributes).delete(xml_name)
|
|
65
|
-
base.
|
|
76
|
+
base.class_attributes.delete(attr_name)
|
|
66
77
|
end
|
|
67
78
|
|
|
68
79
|
XML_BODY = lambda do # rubocop:disable Metrics/BlockLength
|
|
@@ -70,8 +70,14 @@ module Relaton::Calconnect
|
|
|
70
70
|
# @param [Hash] doc
|
|
71
71
|
#
|
|
72
72
|
def parse_page(doc)
|
|
73
|
+
# The document payload carries the whole record, so an unchanged
|
|
74
|
+
# payload skips the scrape and the write (the digest ledger). The
|
|
75
|
+
# payload is only read when the ledger is on.
|
|
76
|
+
return true if @digest_ledger && unchanged?(doc["id"], doc)
|
|
77
|
+
|
|
73
78
|
bib = Scraper.new(@errors).parse_page doc
|
|
74
|
-
write_doc doc["id"], bib
|
|
79
|
+
file = write_doc doc["id"], bib
|
|
80
|
+
note_digest doc["id"], doc, file: file if @digest_ledger
|
|
75
81
|
true
|
|
76
82
|
rescue StandardError => e
|
|
77
83
|
Util.warn "Document: #{doc['id']}"
|
|
@@ -95,7 +101,9 @@ module Relaton::Calconnect
|
|
|
95
101
|
# Write first, index second: an id pubid rejects is skipped from the
|
|
96
102
|
# index, and the document still has to reach disk.
|
|
97
103
|
File.write file, serialize(bib), encoding: "UTF-8"
|
|
104
|
+
record_written file
|
|
98
105
|
add_to_index bib, file
|
|
106
|
+
file
|
|
99
107
|
end
|
|
100
108
|
|
|
101
109
|
#
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Cie
|
|
3
|
+
# CIE publishes its author lists as one inconsistently formatted string:
|
|
4
|
+
# "Surname, I.X." pairs, bare "Forename Surname" runs, Chinese-style
|
|
5
|
+
# "Surname Given" without a comma, compound surnames ("Nilsson
|
|
6
|
+
# Tengelin"), broken hyphens ("Dam- Hansen"), " and " before the final
|
|
7
|
+
# author, and "et al." tails (relaton-cie#5). The comma is overloaded --
|
|
8
|
+
# it separates authors and marks the surname/forename boundary -- so the
|
|
9
|
+
# parse walks comma-separated segments with three rules:
|
|
10
|
+
#
|
|
11
|
+
# 1. a single-word segment binds the following segment as its forenames
|
|
12
|
+
# ("Hemphälä, Hillevi"; initials "A.Sh." bind the same way);
|
|
13
|
+
# 2. a multi-word segment followed by an initials-only segment binds
|
|
14
|
+
# them ("Nilsson Tengelin, M."), because initials never stand alone;
|
|
15
|
+
# 3. any other segment is one author kept whole (completename) --
|
|
16
|
+
# "Glimne Susanne", "Annika K. Jägerbrand", "Pan Jiangen": which word
|
|
17
|
+
# is the surname would be a guess, so none is made.
|
|
18
|
+
module AuthorsParser
|
|
19
|
+
Author = Struct.new(:surname, :forenames, :completename)
|
|
20
|
+
|
|
21
|
+
# Uppercase-led, dot/hyphen/space-separated short parts only:
|
|
22
|
+
# "A.Sh.", "H.T.B", "W-T. M.", "N." -- never "Li" (lowercase tail),
|
|
23
|
+
# "Guillaume" (long undotted part) or "Bierman".
|
|
24
|
+
INITIALS_RX = /\A\p{Lu}(?:[.\s-]+\p{Lu}\p{Ll}{0,2})*\.?\z/.freeze
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
# @param string [String] the CIE authors string
|
|
28
|
+
# @return [Array<Author>]
|
|
29
|
+
def parse(string)
|
|
30
|
+
authors = []
|
|
31
|
+
segments = segmentize(string)
|
|
32
|
+
i = 0
|
|
33
|
+
while i < segments.length
|
|
34
|
+
seg = segments[i]
|
|
35
|
+
nxt = segments[i + 1]
|
|
36
|
+
i += if initials?(seg)
|
|
37
|
+
# Defensive: a stray initials segment binds left.
|
|
38
|
+
authors.last&.forenames << seg
|
|
39
|
+
1
|
|
40
|
+
elsif single_word?(seg) || nxt && initials?(nxt)
|
|
41
|
+
# "Surname, Forename": a single-word segment binds
|
|
42
|
+
# whatever follows; a multi-word one only binds trailing
|
|
43
|
+
# initials, which never stand alone.
|
|
44
|
+
authors << Author.new(seg, nxt ? [nxt] : [], nil)
|
|
45
|
+
nxt ? 2 : 1
|
|
46
|
+
else
|
|
47
|
+
authors << Author.new(nil, nil, seg)
|
|
48
|
+
1
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
authors
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @param seg [String]
|
|
55
|
+
# @return [Boolean]
|
|
56
|
+
def single_word?(seg)
|
|
57
|
+
!seg.include?(" ")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @param seg [String]
|
|
61
|
+
# @return [Boolean]
|
|
62
|
+
def initials?(seg)
|
|
63
|
+
INITIALS_RX.match? seg
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def segmentize(string)
|
|
69
|
+
string
|
|
70
|
+
.gsub(/(\p{L})- (?=\p{Lu})/, '\1-')
|
|
71
|
+
.gsub(%r{\s*,?\s+and\s+}i, ", ")
|
|
72
|
+
.sub(/\s*,?\s*et\s+al\.?\s*\z/i, "")
|
|
73
|
+
.split(/\s*[,;]\s*/).map(&:strip).reject(&:empty?)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -329,28 +329,21 @@ module Relaton
|
|
|
329
329
|
def fetch_contributor(doc) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity
|
|
330
330
|
authors = doc.xpath('//hgroup/p[not(@class="pub_date")]').text.gsub "\"", ""
|
|
331
331
|
contribs = []
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
surname = Bib::LocalizedString.new content: sname, language: "en", script: "Latn"
|
|
343
|
-
forename = []
|
|
344
|
-
forename << Bib::FullNameType::Forename.new(content: fname, language: "en", script: "Latn") if fname
|
|
345
|
-
(init&.strip || "").split(/(?:,|\.)(?:-|\s)?/).each do |int|
|
|
346
|
-
forename << Bib::FullNameType::Forename.new(content: "", initial: int.strip, language: "en", script: "Latn")
|
|
347
|
-
end
|
|
348
|
-
fullname = Bib::FullName.new surname: surname, forename: forename
|
|
332
|
+
AuthorsParser.parse(authors).each do |author|
|
|
333
|
+
fullname =
|
|
334
|
+
if author.completename
|
|
335
|
+
Bib::FullName.new completename: name_string(author.completename)
|
|
336
|
+
else
|
|
337
|
+
Bib::FullName.new(
|
|
338
|
+
surname: name_string(author.surname),
|
|
339
|
+
forename: author.forenames.map { |f| forename(f) },
|
|
340
|
+
)
|
|
341
|
+
end
|
|
349
342
|
person = Bib::Person.new name: fullname
|
|
350
343
|
role = Bib::Contributor::Role.new type: "author"
|
|
351
344
|
contribs << Bib::Contributor.new(person: person, role: [role])
|
|
352
|
-
@errors[:contributor_author] &&= contribs.empty?
|
|
353
345
|
end
|
|
346
|
+
@errors[:contributor_author] &&= contribs.empty?
|
|
354
347
|
org_name = Bib::TypedLocalizedString.new(content: "Commission Internationale de L'Eclairage")
|
|
355
348
|
abbrev = Bib::LocalizedString.new content: "CIE"
|
|
356
349
|
org_uri = Bib::Uri.new content: "cie.co.at"
|
|
@@ -359,6 +352,18 @@ module Relaton
|
|
|
359
352
|
contribs << Bib::Contributor.new(organization: org, role: [org_role])
|
|
360
353
|
end
|
|
361
354
|
|
|
355
|
+
def name_string(str)
|
|
356
|
+
Bib::LocalizedString.new content: str, language: "en", script: "Latn"
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def forename(str)
|
|
360
|
+
if AuthorsParser.initials?(str)
|
|
361
|
+
Bib::FullNameType::Forename.new(initial: str.sub(/\.\z/, ""))
|
|
362
|
+
else
|
|
363
|
+
Bib::FullNameType::Forename.new(content: str)
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
362
367
|
def fetch_ext
|
|
363
368
|
Ext.new(doctype: fetch_doctype, flavor: "cie")
|
|
364
369
|
end
|
data/lib/relaton/cie.rb
CHANGED
|
@@ -36,6 +36,17 @@ module Relaton
|
|
|
36
36
|
# @return [Boolean] whether this run prunes the existing output tree
|
|
37
37
|
attr_accessor :prune
|
|
38
38
|
|
|
39
|
+
# Record a file this run wrote through a flavor's own writer, so the
|
|
40
|
+
# ledger's save (which keeps only files this run owned) and
|
|
41
|
+
# #prune_unwritten account for it. Core's write_unique records its
|
|
42
|
+
# own writes; a flavor writing directly calls this after File.write.
|
|
43
|
+
#
|
|
44
|
+
# @param file [String]
|
|
45
|
+
# @return [void]
|
|
46
|
+
def record_written(file)
|
|
47
|
+
@written << file
|
|
48
|
+
end
|
|
49
|
+
|
|
39
50
|
# @return [Boolean] whether this run consults and maintains the
|
|
40
51
|
# content-digest ledger (see #unchanged?). Off by default: a flavor
|
|
41
52
|
# turns it on only where its enumeration entry determines the record
|
|
@@ -91,6 +102,16 @@ module Relaton
|
|
|
91
102
|
ledger_new[key] = { "digest" => entry_digest(entry), "file" => file }
|
|
92
103
|
end
|
|
93
104
|
|
|
105
|
+
# The file a previous run recorded for `key`, for a caller that
|
|
106
|
+
# skipped the record via #unchanged? and still needs to know where it
|
|
107
|
+
# lives (an alias pointing at another record's file).
|
|
108
|
+
#
|
|
109
|
+
# @param key [String]
|
|
110
|
+
# @return [String, nil]
|
|
111
|
+
def ledger_file(key)
|
|
112
|
+
ledger.dig(key, "file")
|
|
113
|
+
end
|
|
114
|
+
|
|
94
115
|
# Persist the ledger: this run's noted entries over the loaded ones,
|
|
95
116
|
# keeping only entries whose file this run recorded as written.
|
|
96
117
|
#
|
|
@@ -13,12 +13,14 @@ module Relaton
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def fetch(_source = nil)
|
|
16
|
+
docs = []
|
|
16
17
|
Dir["iana-registries/**/*.xml"].each do |file|
|
|
17
18
|
content = File.read file, encoding: "UTF-8"
|
|
18
|
-
|
|
19
|
+
collect content, docs if content.include? "<registry"
|
|
19
20
|
rescue StandardError => e
|
|
20
21
|
Util.error "Error: #{e.message}. File: #{file}"
|
|
21
22
|
end
|
|
23
|
+
write_docs docs
|
|
22
24
|
index.save
|
|
23
25
|
report_errors
|
|
24
26
|
end
|
|
@@ -50,12 +52,57 @@ module Relaton
|
|
|
50
52
|
Util.warn "Skipping index entry for `#{docnumber}` (#{file}): #{e.message}"
|
|
51
53
|
end
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
# A registry element with no <record> children carries no registry data
|
|
56
|
+
# of its own: IANA wraps some registries in a same-titled <hide/> page
|
|
57
|
+
# (the language-tag-extensions wrapper around its "-1" child), and
|
|
58
|
+
# republishes child registries as standalone pages whose XML is empty
|
|
59
|
+
# (rpki-signed-objects). The saved record for such an element is a
|
|
60
|
+
# slug-titled, dateless duplicate of the registry that holds the
|
|
61
|
+
# records (relaton-iana#8, #9), so it never becomes a document: when a
|
|
62
|
+
# record-bearing document of the same registry id exists, it becomes an
|
|
63
|
+
# index alias to that document's file; otherwise it is dropped.
|
|
64
|
+
RegistryDoc = Struct.new(:bib, :empty, :source)
|
|
65
|
+
|
|
66
|
+
def collect(content, docs)
|
|
54
67
|
xml = Moxml.parse(content)
|
|
55
68
|
registry = xml.at_xpath("/xmlns:registry", NS)
|
|
56
69
|
doc = Parser.parse registry, nil, @errors
|
|
57
|
-
|
|
58
|
-
registry.xpath("./xmlns:registry", NS).each
|
|
70
|
+
docs << RegistryDoc.new(doc, recordless?(registry), content) if doc
|
|
71
|
+
registry.xpath("./xmlns:registry", NS).each do |r|
|
|
72
|
+
child = Parser.parse r, doc, @errors
|
|
73
|
+
docs << RegistryDoc.new(child, recordless?(r), content) if child
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def recordless?(registry_el)
|
|
78
|
+
registry_el.xpath("./xmlns:record", NS).empty?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def write_docs(docs)
|
|
82
|
+
saved = {}
|
|
83
|
+
docs.each do |d|
|
|
84
|
+
if d.empty
|
|
85
|
+
Util.warn "Registry #{d.bib.docnumber} has no records; skipping"
|
|
86
|
+
next
|
|
87
|
+
end
|
|
88
|
+
# The fetched XML content bounds the registry's record, so an
|
|
89
|
+
# unchanged source skips the parse and the write (the digest
|
|
90
|
+
# ledger); the aliased file still has to resolve for the alias
|
|
91
|
+
# phase. The content is only digested when the ledger is on.
|
|
92
|
+
if @digest_ledger && d.source && unchanged?(d.bib.docnumber, d.source)
|
|
93
|
+
saved[d.bib.id] = ledger_file(d.bib.docnumber)
|
|
94
|
+
next
|
|
95
|
+
end
|
|
96
|
+
file = save_doc d.bib
|
|
97
|
+
note_digest d.bib.docnumber, d.source, file: file if @digest_ledger && d.source
|
|
98
|
+
saved[d.bib.id] = file if file
|
|
99
|
+
end
|
|
100
|
+
docs.select(&:empty).each do |d|
|
|
101
|
+
next unless (file = saved[d.bib.id])
|
|
102
|
+
|
|
103
|
+
Util.warn "Registry #{d.bib.docnumber} has no records; aliasing #{file}"
|
|
104
|
+
add_to_index d.bib.docnumber, file
|
|
105
|
+
end
|
|
59
106
|
end
|
|
60
107
|
|
|
61
108
|
#
|
|
@@ -83,6 +130,8 @@ module Relaton
|
|
|
83
130
|
@files << file
|
|
84
131
|
add_to_index bib.docnumber, file
|
|
85
132
|
File.write file, serialize(bib), encoding: "UTF-8"
|
|
133
|
+
record_written file
|
|
134
|
+
file
|
|
86
135
|
end
|
|
87
136
|
|
|
88
137
|
def to_xml(bib)
|
data/lib/relaton/ieee/CLAUDE.md
CHANGED
|
@@ -24,10 +24,36 @@ serialized to the `_type: pubid:ieee:*` (lutaml) form, keyed on the index via
|
|
|
24
24
|
loss is never silent. `DataFetcher.fetch` (doc generation) is unchanged — the
|
|
25
25
|
data repo (`relaton-data-ieee`) calls `build_index` after obtaining `data/`.
|
|
26
26
|
- **Read side.** `bibliography.rb#search` passes `pubid_class:` to `find_or_create`,
|
|
27
|
-
parses the query with `parse_pubid` (falling back to the raw String on failure)
|
|
28
|
-
passes the pubid to `index.search` for number-narrowing
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
parses the query with `parse_pubid` (falling back to the raw String on failure)
|
|
28
|
+
and passes the pubid to `index.search` for number-narrowing. A parsed query
|
|
29
|
+
selects its rows by **pubid equality with the year aside**
|
|
30
|
+
(`id.exclude(:year) == pubid.exclude(:year)`), so a redline, a draft, a
|
|
31
|
+
corrigendum and another number (`1003.10` for `1003.1`) never match. A dated
|
|
32
|
+
query (its own year, or the `year` argument) takes that year; an **undated**
|
|
33
|
+
query takes the **latest** edition, and `get` returns it as
|
|
34
|
+
`to_most_recent_reference` (undated IEEE docids, no date, an `instanceOf`
|
|
35
|
+
relation to the edition; `opts[:keep_year]` keeps it dated) — relaton-ieee#29.
|
|
36
|
+
Before this, a substring match + `min_by(to_s)` answered `IEEE 528` with the
|
|
37
|
+
oldest edition, `528-1994`. When equality finds nothing (an unparseable query,
|
|
38
|
+
or one of the ~4% of forms whose year pubid keeps elsewhere, e.g.
|
|
39
|
+
`IEEE Std 1003.1-1988/INT-1992`, the corrigendum's legacy base year), the old
|
|
40
|
+
substring match is the fallback. `processor.rb#remove_index_file` passes
|
|
41
|
+
`url: true` and `file:` only: the delete never reads the index (see
|
|
42
|
+
`lib/relaton/index/CLAUDE.md`).
|
|
43
|
+
- **Why not `pubid === row`.** pubid declares no `subset_strict` attribute for
|
|
44
|
+
IEEE, so a nil `parts`, `prefix`, `draft` or `draft_status` in the query is a
|
|
45
|
+
wildcard: over the published index (12,616 rows) `===` let `IEEE 802.3` match
|
|
46
|
+
`802.3.1-2011` and `IEEE 754` match `IEEE Unapproved P754/D9.0`. Switch to
|
|
47
|
+
`index.search(pubid)` once pubid makes those strict, and re-measure `===`
|
|
48
|
+
against the equality over the full index first.
|
|
49
|
+
- **`Relaton::Ieee::Docidentifier`.** `Item` holds this class (the producer
|
|
50
|
+
creates it too: `IdamsParser#docidentifier`, `DataFetcher`), because
|
|
51
|
+
`Bib::Docidentifier#remove_date!` is a no-op. Its `remove_date!` re-renders an
|
|
52
|
+
`IEEE` docid through pubid without the year (`to_s(trademark: true)` for the
|
|
53
|
+
`scope: trademark` one) and leaves an id **with no year** untouched: a
|
|
54
|
+
re-render would change a draft's spelling (`P802.5t™/D-2.5` →
|
|
55
|
+
`P802.5t®/D2.5`). `ItemData#to_most_recent_reference` drops the ISBN and
|
|
56
|
+
DOI, which name one edition.
|
|
31
57
|
|
|
32
58
|
## Identifier resolution (`RawbibIdParser`, pubid-first)
|
|
33
59
|
|
|
@@ -9,33 +9,12 @@ module Relaton
|
|
|
9
9
|
#
|
|
10
10
|
# @param ref [String, Pubid::Ieee::Identifier] the reference, or its
|
|
11
11
|
# parse from Relaton::Db (relaton#205)
|
|
12
|
+
# @param year [String, nil] the year, when the reference has none
|
|
12
13
|
#
|
|
13
14
|
# @return [Relaton::Ieee::ItemData, nil]
|
|
14
15
|
#
|
|
15
|
-
def search(ref
|
|
16
|
-
|
|
17
|
-
# pilot's contract): a parsed query reads only its own shard. A
|
|
18
|
-
# Pages failure raises `Relaton::RequestError`; the whole-index
|
|
19
|
-
# zip on the same site serves only the retry on a miss.
|
|
20
|
-
index = Relaton::Index.find_or_create :ieee, pages_url: PAGES_URL,
|
|
21
|
-
pubid_class: ::Pubid::Ieee::Identifier
|
|
22
|
-
# Pass the parsed pubid (not the raw String) so index-v2 narrows
|
|
23
|
-
# candidates by number via binary search before the block runs; the
|
|
24
|
-
# block keeps the broad substring match the string index gave, and an
|
|
25
|
-
# unparseable/partial ref falls back to the full-scan String search.
|
|
26
|
-
# Rows are Pubid::Ieee::Identifier objects (not Comparable), so pick by
|
|
27
|
-
# the string form.
|
|
28
|
-
pubid = parse_pubid ref
|
|
29
|
-
needle = pubid.to_s
|
|
30
|
-
# pubid's normalization moves a legacy base year into its own
|
|
31
|
-
# comma segment ("IEEE P802.16/D5, 2004/Cor. 1-2005"); the
|
|
32
|
-
# index rows carry the canonical form without it, so the
|
|
33
|
-
# block also matches with the moved year stripped
|
|
34
|
-
row = index.search(pubid) do |r|
|
|
35
|
-
id = r[:id].to_s
|
|
36
|
-
id.include?(needle) ||
|
|
37
|
-
id.include?(needle.gsub(/, (19|20)\d{2}\//, "/"))
|
|
38
|
-
end.min_by { |r| r[:id].to_s }
|
|
16
|
+
def search(ref, year = nil)
|
|
17
|
+
row = find_row parse_pubid(ref), year
|
|
39
18
|
return unless row
|
|
40
19
|
|
|
41
20
|
resp = Faraday.get "#{GH_URL}#{row[:file]}"
|
|
@@ -47,30 +26,106 @@ module Relaton
|
|
|
47
26
|
end
|
|
48
27
|
|
|
49
28
|
#
|
|
50
|
-
# Get IEEE bibliography item by reference.
|
|
29
|
+
# Get IEEE bibliography item by reference. An undated reference
|
|
30
|
+
# answers with the latest edition as an undated item that has an
|
|
31
|
+
# `instanceOf` relation to that edition (relaton-ieee#29).
|
|
51
32
|
#
|
|
52
33
|
# @param ref [String, Pubid::Ieee::Identifier] the IEEE standard
|
|
53
34
|
# reference to look up (e.g. "IEEE 528-2019"), or its parse from
|
|
54
35
|
# Relaton::Db (relaton#205)
|
|
55
36
|
# @param year [String] the year the standard was published (optional)
|
|
56
37
|
# @param opts [Hash] options
|
|
38
|
+
# @option opts [Boolean] :keep_year keep the edition's date on an
|
|
39
|
+
# undated reference
|
|
57
40
|
#
|
|
58
41
|
# @return [Relaton::Ieee::ItemData, nil]
|
|
59
42
|
#
|
|
60
|
-
def get(ref,
|
|
43
|
+
def get(ref, year = nil, opts = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
61
44
|
Util.info "Fetching from Relaton repository ...", key: ref.to_s
|
|
62
|
-
item = search(ref)
|
|
63
|
-
|
|
64
|
-
Util.info "Found: `#{item.docidentifier.first.content}`", key: ref.to_s
|
|
65
|
-
item
|
|
66
|
-
else
|
|
45
|
+
item = search(ref, year)
|
|
46
|
+
unless item
|
|
67
47
|
Util.info "Not found.", key: ref.to_s
|
|
68
|
-
|
|
48
|
+
return
|
|
69
49
|
end
|
|
50
|
+
|
|
51
|
+
Util.info "Found: `#{item.docidentifier.first.content}`", key: ref.to_s
|
|
52
|
+
return item if opts[:keep_year] || dated?(parse_pubid(ref), year)
|
|
53
|
+
|
|
54
|
+
item.to_most_recent_reference
|
|
70
55
|
end
|
|
71
56
|
|
|
72
57
|
private
|
|
73
58
|
|
|
59
|
+
# The index row of the reference. A parsed reference matches the rows
|
|
60
|
+
# of its own document by pubid equality, the year aside; an undated
|
|
61
|
+
# one takes the latest edition. When that finds nothing (an
|
|
62
|
+
# unparseable reference, or a form whose year pubid keeps elsewhere)
|
|
63
|
+
# the broad substring match is the fallback.
|
|
64
|
+
#
|
|
65
|
+
# @param pubid [Pubid::Ieee::Identifier, String]
|
|
66
|
+
# @param year [String, nil]
|
|
67
|
+
# @return [Hash, nil] the `{ id:, file: }` row
|
|
68
|
+
def find_row(pubid, year)
|
|
69
|
+
index = Relaton::Index.find_or_create :ieee, pages_url: PAGES_URL,
|
|
70
|
+
pubid_class: ::Pubid::Ieee::Identifier
|
|
71
|
+
row = pubid_row(index, pubid, year) unless pubid.is_a?(String)
|
|
72
|
+
row || substring_row(index, pubid)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Not `pubid === row`: IEEE `parts`, `prefix`, `draft` and
|
|
76
|
+
# `draft_status` are not `subset_strict` in pubid, so a nil there is a
|
|
77
|
+
# wildcard and `IEEE 802.3` would match `IEEE Std 802.3.1-2011` and
|
|
78
|
+
# `IEEE Unapproved P802.3/D2.0`.
|
|
79
|
+
def pubid_row(index, pubid, year)
|
|
80
|
+
target = (pubid.year || year)&.to_s
|
|
81
|
+
base = pubid.exclude(:year)
|
|
82
|
+
rows = index.search(pubid) do |r|
|
|
83
|
+
id = r[:id]
|
|
84
|
+
id.is_a?(::Pubid::Ieee::Identifier) && id.exclude(:year) == base &&
|
|
85
|
+
(target.nil? || id.year.to_s == target)
|
|
86
|
+
end
|
|
87
|
+
return rows.min_by { |r| r[:id].to_s } if target
|
|
88
|
+
|
|
89
|
+
rows.max_by { |r| [r[:id].year.to_s, r[:id].to_s] }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# pubid's normalization moves a legacy base year into its own comma
|
|
93
|
+
# segment ("IEEE P802.16/D5, 2004/Cor. 1-2005"); the index rows carry
|
|
94
|
+
# the canonical form without it, so the block also matches with the
|
|
95
|
+
# moved year stripped. Rows are Pubid::Ieee::Identifier objects (not
|
|
96
|
+
# Comparable), so pick by the string form.
|
|
97
|
+
#
|
|
98
|
+
# The project prefix moves too: "IEEE P802.21/D14" parses as a
|
|
99
|
+
# ProjectDraft, but the pre-publication crawl records spell the same
|
|
100
|
+
# document "IEEE Unapproved 802.21/D14, Sept 2008" — the Unapproved
|
|
101
|
+
# draft status occupies the slot the publisher and P would render in,
|
|
102
|
+
# so the row string contains neither (relaton-ieee#25). The draft
|
|
103
|
+
# number "/D14" still has to match, so the bare form cannot reach the
|
|
104
|
+
# published standard or a different draft — the variant exists only
|
|
105
|
+
# for a reference that names its draft.
|
|
106
|
+
def substring_row(index, pubid)
|
|
107
|
+
needle = pubid.to_s
|
|
108
|
+
# The publisher-and-prefix-stripped form; nil when there is no
|
|
109
|
+
# project-prefixed draft number.
|
|
110
|
+
stripped = needle.sub("IEEE P", "") if needle.match?(/\AIEEE P\S*\/D\d/i)
|
|
111
|
+
index.search(pubid) do |r|
|
|
112
|
+
id = r[:id].to_s
|
|
113
|
+
id.include?(needle) ||
|
|
114
|
+
id.include?(needle.gsub(/, (19|20)\d{2}\//, "/")) ||
|
|
115
|
+
(stripped && id.include?(stripped))
|
|
116
|
+
end.min_by { |r| r[:id].to_s }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# @param pubid [Pubid::Ieee::Identifier, String]
|
|
120
|
+
# @param year [String, nil]
|
|
121
|
+
def dated?(pubid, year)
|
|
122
|
+
return true if year
|
|
123
|
+
return pubid.year if pubid.respond_to?(:year)
|
|
124
|
+
|
|
125
|
+
# An unparseable reference was found by substring; leave it dated.
|
|
126
|
+
true
|
|
127
|
+
end
|
|
128
|
+
|
|
74
129
|
# Parse a reference into a Pubid::Ieee::Identifier for index narrowing, or
|
|
75
130
|
# return the raw String when pubid can't parse it (e.g. a partial ref) so
|
|
76
131
|
# the search falls back to the substring scan.
|
|
@@ -171,7 +171,7 @@ module Relaton
|
|
|
171
171
|
end
|
|
172
172
|
return if RELATION_TYPES[type] == false
|
|
173
173
|
|
|
174
|
-
docid =
|
|
174
|
+
docid = Docidentifier.new(type: "IEEE", content: fref, primary: true)
|
|
175
175
|
bib = ItemData.new formattedref: Bib::Formattedref.new(content: fref), docidentifier: [docid]
|
|
176
176
|
description = create_relation_description type
|
|
177
177
|
Bib::Relation.new(type: RELATION_TYPES[type][:type], description: description, bibitem: bib)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Relaton
|
|
2
|
+
module Ieee
|
|
3
|
+
# IEEE document identifier. An `IEEE` docid is backed by a
|
|
4
|
+
# Pubid::Ieee::Identifier so `remove_date!` can strip the year when an
|
|
5
|
+
# undated reference answers with its latest edition (relaton-ieee#29).
|
|
6
|
+
# ISBN and DOI docids carry no pubid and are never changed.
|
|
7
|
+
class Docidentifier < Bib::Docidentifier
|
|
8
|
+
# Strip the year: `IEEE Std 528-2019` -> `IEEE Std 528`, and the
|
|
9
|
+
# trademark docid `IEEE Std 528™-2019` -> `IEEE Std 528™`. An id with no
|
|
10
|
+
# year is left as it is: a re-render would change its spelling
|
|
11
|
+
# (`IEEE P802.5t™/D-2.5` -> `IEEE P802.5t®/D2.5`).
|
|
12
|
+
def remove_date!
|
|
13
|
+
pubid = parse_pubid
|
|
14
|
+
return unless pubid&.year
|
|
15
|
+
|
|
16
|
+
undated = pubid.exclude(:year)
|
|
17
|
+
self.content = scope == "trademark" ? undated.to_s(trademark: true) : undated.to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# IEEE has no all-parts form: both are no-ops, so
|
|
21
|
+
# `Bib::ItemData#to_all_parts` never raises.
|
|
22
|
+
def remove_part!; end
|
|
23
|
+
|
|
24
|
+
def to_all_parts!; end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def parse_pubid
|
|
29
|
+
return unless type == "IEEE" && content
|
|
30
|
+
|
|
31
|
+
::Pubid::Ieee::Identifier.parse content
|
|
32
|
+
rescue StandardError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -92,7 +92,7 @@ module Relaton
|
|
|
92
92
|
#
|
|
93
93
|
# Parse identifiers
|
|
94
94
|
#
|
|
95
|
-
# @return [Array<Relaton::
|
|
95
|
+
# @return [Array<Relaton::Ieee::Docidentifier>]
|
|
96
96
|
#
|
|
97
97
|
def parse_docidentifier # rubocop:disable Metrics/MethodLength
|
|
98
98
|
ids = @doc.isbn_doi.map { |id| id[:content] = id.delete(:id); id }
|
|
@@ -102,7 +102,7 @@ module Relaton
|
|
|
102
102
|
ids.unshift(content: pubid.to_s, type: "IEEE", primary: true)
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
-
result = ids.map { |dcid|
|
|
105
|
+
result = ids.map { |dcid| Docidentifier.new(**dcid) }
|
|
106
106
|
@errors[:docidentifier] &&= result.empty?
|
|
107
107
|
result
|
|
108
108
|
end
|
data/lib/relaton/ieee/item.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative "ext"
|
|
2
|
+
require_relative "docidentifier"
|
|
2
3
|
|
|
3
4
|
module Relaton
|
|
4
5
|
module Ieee
|
|
@@ -6,6 +7,8 @@ module Relaton
|
|
|
6
7
|
model ItemData
|
|
7
8
|
|
|
8
9
|
attribute :ext, Ext
|
|
10
|
+
attribute :docidentifier, Docidentifier, collection: true,
|
|
11
|
+
initialize_empty: true
|
|
9
12
|
end
|
|
10
13
|
end
|
|
11
14
|
end
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
module Relaton
|
|
2
2
|
module Ieee
|
|
3
3
|
class ItemData < Bib::ItemData
|
|
4
|
-
|
|
4
|
+
# The ISBN and DOI name one edition, so the undated item keeps only the
|
|
5
|
+
# IEEE docids; its `instanceOf` relation still carries them.
|
|
6
|
+
def to_most_recent_reference
|
|
7
|
+
me = super
|
|
8
|
+
me.docidentifier = me.docidentifier.select { |d| d.type == "IEEE" }
|
|
9
|
+
me
|
|
10
|
+
end
|
|
5
11
|
end
|
|
6
12
|
end
|
|
7
13
|
end
|
data/lib/relaton/jis.rb
CHANGED
|
@@ -356,7 +356,7 @@ module Relaton
|
|
|
356
356
|
end
|
|
357
357
|
|
|
358
358
|
index = Relaton::Index.find_or_create(
|
|
359
|
-
:nist,
|
|
359
|
+
:nist, pages_url: PAGES_URL,
|
|
360
360
|
pubid_class: ::Pubid::Nist::Identifier
|
|
361
361
|
)
|
|
362
362
|
# search(r) narrows candidates by number via binary search; the block
|
data/lib/relaton/nist.rb
CHANGED
|
@@ -22,6 +22,7 @@ module Relaton
|
|
|
22
22
|
# (for older gem versions) is rebuilt separately by the relaton-data-nist
|
|
23
23
|
# crawler, not by this gem's DataFetcher.
|
|
24
24
|
INDEXFILE = "index-v2"
|
|
25
|
+
PAGES_URL = "https://relaton.github.io/relaton-data-nist/"
|
|
25
26
|
|
|
26
27
|
class Error < StandardError; end
|
|
27
28
|
|
data/lib/relaton/plateau.rb
CHANGED
data/lib/relaton/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: relaton
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|
|
@@ -422,6 +422,26 @@ dependencies:
|
|
|
422
422
|
- - "~>"
|
|
423
423
|
- !ruby/object:Gem::Version
|
|
424
424
|
version: '1.26'
|
|
425
|
+
- !ruby/object:Gem::Dependency
|
|
426
|
+
name: parsanol
|
|
427
|
+
requirement: !ruby/object:Gem::Requirement
|
|
428
|
+
requirements:
|
|
429
|
+
- - ">="
|
|
430
|
+
- !ruby/object:Gem::Version
|
|
431
|
+
version: 1.3.112
|
|
432
|
+
- - "<"
|
|
433
|
+
- !ruby/object:Gem::Version
|
|
434
|
+
version: '1.4'
|
|
435
|
+
type: :runtime
|
|
436
|
+
prerelease: false
|
|
437
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
438
|
+
requirements:
|
|
439
|
+
- - ">="
|
|
440
|
+
- !ruby/object:Gem::Version
|
|
441
|
+
version: 1.3.112
|
|
442
|
+
- - "<"
|
|
443
|
+
- !ruby/object:Gem::Version
|
|
444
|
+
version: '1.4'
|
|
425
445
|
- !ruby/object:Gem::Dependency
|
|
426
446
|
name: parslet
|
|
427
447
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -556,6 +576,7 @@ files:
|
|
|
556
576
|
- lib/relaton/3gpp/bibitem.rb
|
|
557
577
|
- lib/relaton/3gpp/bibliography.rb
|
|
558
578
|
- lib/relaton/3gpp/data_fetcher.rb
|
|
579
|
+
- lib/relaton/3gpp/date.rb
|
|
559
580
|
- lib/relaton/3gpp/docidentifier.rb
|
|
560
581
|
- lib/relaton/3gpp/doctype.rb
|
|
561
582
|
- lib/relaton/3gpp/ext.rb
|
|
@@ -749,6 +770,7 @@ files:
|
|
|
749
770
|
- lib/relaton/cie.rb
|
|
750
771
|
- lib/relaton/cie/CLAUDE.md
|
|
751
772
|
- lib/relaton/cie/README.adoc
|
|
773
|
+
- lib/relaton/cie/authors_parser.rb
|
|
752
774
|
- lib/relaton/cie/bibdata.rb
|
|
753
775
|
- lib/relaton/cie/bibitem.rb
|
|
754
776
|
- lib/relaton/cie/bibliography.rb
|
|
@@ -953,6 +975,7 @@ files:
|
|
|
953
975
|
- lib/relaton/ieee/converter/bibxml/from_rfcxml.rb
|
|
954
976
|
- lib/relaton/ieee/converter/bibxml/from_rfcxml_referencegroup.rb
|
|
955
977
|
- lib/relaton/ieee/data_fetcher.rb
|
|
978
|
+
- lib/relaton/ieee/docidentifier.rb
|
|
956
979
|
- lib/relaton/ieee/doctype.rb
|
|
957
980
|
- lib/relaton/ieee/editorial_group.rb
|
|
958
981
|
- lib/relaton/ieee/ext.rb
|