relaton 3.0.0.pre.alpha.4 → 3.0.0.pre.alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/relaton/adobe/processor.rb +9 -0
- data/lib/relaton/bib/converter/csl.rb +110 -0
- data/lib/relaton/bib/converter/ris.rb +104 -0
- data/lib/relaton/bib/converter/titles.rb +24 -0
- data/lib/relaton/bib/item_data.rb +9 -0
- data/lib/relaton/bib/model/item.rb +6 -6
- data/lib/relaton/bib/sanitizer.rb +71 -30
- data/lib/relaton/bib.rb +10 -0
- data/lib/relaton/bipm/processor.rb +1 -0
- data/lib/relaton/bipm/rawdata_bipm_metrologia/affiliations.rb +6 -6
- data/lib/relaton/bipm/si_brochure_parser.rb +2 -2
- data/lib/relaton/bsi/processor.rb +5 -0
- data/lib/relaton/ccsds/bibliography.rb +1 -0
- data/lib/relaton/ccsds/processor.rb +12 -0
- data/lib/relaton/cen/hit_collection.rb +1 -1
- data/lib/relaton/cen/processor.rb +5 -0
- data/lib/relaton/cen/scraper.rb +9 -9
- data/lib/relaton/cie/data_fetcher.rb +18 -18
- data/lib/relaton/cie/processor.rb +1 -0
- data/lib/relaton/cie.rb +1 -1
- data/lib/relaton/cloud.rb +127 -0
- data/lib/relaton/core/hit_collection.rb +6 -10
- data/lib/relaton/core/processor.rb +68 -0
- data/lib/relaton/db/cache.rb +444 -148
- data/lib/relaton/db/cache_entry.rb +33 -0
- data/lib/relaton/db/registry.rb +52 -0
- data/lib/relaton/db.rb +131 -72
- data/lib/relaton/doi/crossref.rb +23 -6
- data/lib/relaton/doi/processor.rb +1 -0
- data/lib/relaton/easc/processor.rb +1 -0
- data/lib/relaton/ecma/data_fetcher.rb +1 -1
- data/lib/relaton/ecma/data_parser.rb +1 -1
- data/lib/relaton/ecma/edition_parser.rb +2 -2
- data/lib/relaton/ecma/memento_parser.rb +4 -4
- data/lib/relaton/ecma/standard_parser.rb +6 -6
- data/lib/relaton/etsi/processor.rb +1 -0
- data/lib/relaton/gb/gb_scraper.rb +5 -5
- data/lib/relaton/gb/scraper.rb +16 -16
- data/lib/relaton/gb/sec_scraper.rb +8 -8
- data/lib/relaton/gb/t_scraper.rb +5 -5
- data/lib/relaton/gost/processor.rb +1 -0
- data/lib/relaton/iala/processor.rb +1 -0
- data/lib/relaton/iana/data_fetcher.rb +3 -3
- data/lib/relaton/iana/parser.rb +8 -4
- data/lib/relaton/iana/processor.rb +9 -0
- data/lib/relaton/iec/data_parser.rb +24 -9
- data/lib/relaton/iec/processor.rb +6 -0
- data/lib/relaton/iec.rb +1 -1
- data/lib/relaton/ieee/data_fetcher.rb +1 -1
- data/lib/relaton/ieee/processor.rb +8 -0
- data/lib/relaton/ietf/data_fetcher.rb +1 -1
- data/lib/relaton/ietf/processor.rb +1 -0
- data/lib/relaton/ietf/rfc/entry.rb +15 -19
- data/lib/relaton/iho/processor.rb +1 -0
- data/lib/relaton/index/file_io.rb +2 -2
- data/lib/relaton/index/pool.rb +4 -3
- data/lib/relaton/index/shard_source.rb +1 -1
- data/lib/relaton/index/type.rb +2 -5
- data/lib/relaton/isbn/open_library.rb +11 -7
- data/lib/relaton/isbn/processor.rb +10 -0
- data/lib/relaton/iso/data_parser.rb +2 -2
- data/lib/relaton/iso/processor.rb +5 -0
- data/lib/relaton/iso/scraper.rb +20 -20
- data/lib/relaton/itu/bibliography.rb +99 -13
- data/lib/relaton/itu/data_crawler_r.rb +2 -2
- data/lib/relaton/itu/hit_collection.rb +64 -52
- data/lib/relaton/itu/processor.rb +1 -0
- data/lib/relaton/itu/scraper.rb +13 -3
- data/lib/relaton/itu.rb +0 -2
- data/lib/relaton/jis/data_fetcher.rb +4 -4
- data/lib/relaton/jis/processor.rb +1 -0
- data/lib/relaton/jis/scraper.rb +8 -8
- data/lib/relaton/oasis/browser_agent.rb +2 -2
- data/lib/relaton/oasis/data_parser.rb +5 -5
- data/lib/relaton/oasis/data_parser_utils.rb +2 -2
- data/lib/relaton/oasis/data_part_parser.rb +7 -7
- data/lib/relaton/ogc/processor.rb +7 -0
- data/lib/relaton/oiml/processor.rb +1 -0
- data/lib/relaton/omg/scraper.rb +11 -11
- data/lib/relaton/omg.rb +1 -1
- data/lib/relaton/plateau/processor.rb +1 -0
- data/lib/relaton/un/bibliography.rb +21 -10
- data/lib/relaton/un/processor.rb +1 -0
- data/lib/relaton/version.rb +1 -1
- data/lib/relaton/w3c/processor.rb +1 -0
- data/lib/relaton.rb +13 -0
- metadata +48 -16
- data/lib/relaton/itu/pubid.rb +0 -199
data/lib/relaton/cen/scraper.rb
CHANGED
|
@@ -53,7 +53,7 @@ module Relaton
|
|
|
53
53
|
# @param doc [Mechanize::Page]
|
|
54
54
|
# @return [Array<Relaton::Bib::LocalizedMarkedUpString>]
|
|
55
55
|
def fetch_abstract(doc)
|
|
56
|
-
content = doc.
|
|
56
|
+
content = doc.at_xpath("//tr[th[.='Abstract/Scope']]/td")
|
|
57
57
|
[Bib::Abstract.new(content: content.text, language: "en", script: "Latn")]
|
|
58
58
|
end
|
|
59
59
|
|
|
@@ -68,7 +68,7 @@ module Relaton
|
|
|
68
68
|
# @param doc [Mechanize::Page]
|
|
69
69
|
# @return [Relaton::Bib::DocumentStatus, nil]
|
|
70
70
|
def fetch_status(doc)
|
|
71
|
-
s = doc.
|
|
71
|
+
s = doc.at_xpath("//tr[th[.='Status']]/td")
|
|
72
72
|
return unless s
|
|
73
73
|
|
|
74
74
|
stage = Bib::Status::Stage.new(content: s.text.strip)
|
|
@@ -98,7 +98,7 @@ module Relaton
|
|
|
98
98
|
doc.xpath(
|
|
99
99
|
"//div[@id='DASHBOARD_LISTRELATIONS']/table/tr[th[.!='Sales Points']]",
|
|
100
100
|
).each_with_object([]) do |rt, acc|
|
|
101
|
-
type = relation_type rt.
|
|
101
|
+
type = relation_type rt.at_xpath("th").text.downcase
|
|
102
102
|
rt.xpath("td/a").each do |r|
|
|
103
103
|
acc << Bib::Relation.new(type: type, bibitem: create_relation(r))
|
|
104
104
|
end
|
|
@@ -124,7 +124,7 @@ module Relaton
|
|
|
124
124
|
# @param doc [Mechanize::Page]
|
|
125
125
|
# @return [Array<Relaton::Bib::Title>]
|
|
126
126
|
def fetch_titles(doc)
|
|
127
|
-
te = doc.
|
|
127
|
+
te = doc.at_xpath("//tr[th[.='Title']]/td").text.strip
|
|
128
128
|
Bib::Title.from_string te, "en", "Latn"
|
|
129
129
|
end
|
|
130
130
|
|
|
@@ -134,10 +134,10 @@ module Relaton
|
|
|
134
134
|
def fetch_dates(doc) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength
|
|
135
135
|
doc.xpath("//div[@id='DASHBOARD_LISTIMPLEMENTATIONDATES']/table/tr")
|
|
136
136
|
.each_with_object([]) do |d, acc|
|
|
137
|
-
at = d.
|
|
137
|
+
at = d.at_xpath("td").text
|
|
138
138
|
next if at.empty?
|
|
139
139
|
|
|
140
|
-
t = d.
|
|
140
|
+
t = d.at_xpath("th").text
|
|
141
141
|
type = case t
|
|
142
142
|
when /DOR/ then "adapted"
|
|
143
143
|
when /DAV/ then "issued"
|
|
@@ -154,8 +154,8 @@ module Relaton
|
|
|
154
154
|
# @param doc [Mechanize::Page]
|
|
155
155
|
# @return [Array<Relaton::Bib::Contributor>]
|
|
156
156
|
def fetch_contributors(doc)
|
|
157
|
-
code = doc.
|
|
158
|
-
title = doc.
|
|
157
|
+
code = doc.at_xpath("//tr/td/h1/text()").text
|
|
158
|
+
title = doc.at_xpath("//tr/td[3]/h1").text
|
|
159
159
|
%r{/(?<type>\w+)(?:\s(?<num>[^/]+))?$} =~ code
|
|
160
160
|
org = owner_entity
|
|
161
161
|
|
|
@@ -190,7 +190,7 @@ module Relaton
|
|
|
190
190
|
# @param doc [Mechanize::Page]
|
|
191
191
|
# @return [Array<Bib::Copyright>]
|
|
192
192
|
def fetch_copyright(doc)
|
|
193
|
-
date = doc.
|
|
193
|
+
date = doc.at_xpath("//tr[th[.='date of Availability (DAV)']]/td").text
|
|
194
194
|
owner = Bib::ContributionInfo.new(organization: owner_entity)
|
|
195
195
|
from = date.match(/^\d{4}/).to_s
|
|
196
196
|
[Bib::Copyright.new(owner: [owner], from: from)]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "English"
|
|
4
4
|
require "fileutils"
|
|
5
5
|
require "ferrum"
|
|
6
|
-
require "
|
|
6
|
+
require "moxml"
|
|
7
7
|
require "pubid"
|
|
8
8
|
require "relaton/index"
|
|
9
9
|
require "relaton/bib"
|
|
@@ -58,7 +58,7 @@ module Relaton
|
|
|
58
58
|
def get(url)
|
|
59
59
|
@browser.go_to(url)
|
|
60
60
|
wait_for_challenge
|
|
61
|
-
|
|
61
|
+
Moxml.new.parse_html(@browser.body)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
def quit
|
|
@@ -181,7 +181,7 @@ module Relaton
|
|
|
181
181
|
Util.error msg
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
-
# @param hit [
|
|
184
|
+
# @param hit [Moxml::Document]
|
|
185
185
|
# @param doc [Mechanize::Page]
|
|
186
186
|
# @return [Array<Relaton::Bib::Docidentifier>]
|
|
187
187
|
def fetch_docid(hit, doc)
|
|
@@ -200,7 +200,7 @@ module Relaton
|
|
|
200
200
|
else
|
|
201
201
|
@errors[:docid_2] &&= true
|
|
202
202
|
end
|
|
203
|
-
isbn = doc.
|
|
203
|
+
isbn = doc.at_xpath('//h3[contains(.,"ISBN")]/following-sibling::span')&.text
|
|
204
204
|
if isbn && !isbn.strip.empty?
|
|
205
205
|
docid << Bib::Docidentifier.new(type: "ISBN", content: isbn)
|
|
206
206
|
@errors[:docid_isbn] &&= false
|
|
@@ -211,7 +211,7 @@ module Relaton
|
|
|
211
211
|
end
|
|
212
212
|
|
|
213
213
|
def parse_code(hit, doc = nil)
|
|
214
|
-
code = hit.
|
|
214
|
+
code = hit.at_xpath(".//h3/a").text.strip.squeeze(" ").sub(/\u25b9/, "").gsub(" / ", "/")
|
|
215
215
|
c2idx = %r{(?:\(|/)(?<c2>(?:ISO|IEC)\s[^()]+)} =~ code
|
|
216
216
|
code = code[0...c2idx].strip if c2idx
|
|
217
217
|
[primary_code(code, doc), c2]
|
|
@@ -221,7 +221,7 @@ module Relaton
|
|
|
221
221
|
/^(?<code1>[^(]+)(?:\((?<code2>[a-zA-Z]+\d+,(?:\sPages)?[^)]+))?/ =~ code
|
|
222
222
|
if code1&.match?(/^CIE/)
|
|
223
223
|
parse_cie_code code1, code2, doc
|
|
224
|
-
elsif (pcode = doc&.
|
|
224
|
+
elsif (pcode = doc&.at_xpath('//h3[.="Product Code(s):"]/following-sibling::span'))
|
|
225
225
|
"CIE #{pcode.text.strip.match(/[^,]+/)}"
|
|
226
226
|
else
|
|
227
227
|
num = code.match(/(?<=\()\w{2}\d+,.+(?=\))/).to_s.gsub(/,(?=\s)/, "").gsub(/,(?=\S)/, " ")
|
|
@@ -231,7 +231,7 @@ module Relaton
|
|
|
231
231
|
|
|
232
232
|
def parse_cie_code(code1, code2, doc = nil) # rubocop:disable Metrics/CyclomaticComplexity
|
|
233
233
|
code = code1.size > 25 && code2 ? "CIE #{code2.sub(/,(\sPages)?/, '')}" : code1
|
|
234
|
-
add = doc&.
|
|
234
|
+
add = doc&.at_xpath("//hgroup/h2")&.text&.match(/(Add)endum\s(\d+)$/)
|
|
235
235
|
return code unless add
|
|
236
236
|
|
|
237
237
|
"#{code} #{add[1]} #{add[2]}"
|
|
@@ -244,7 +244,7 @@ module Relaton
|
|
|
244
244
|
# @param doc [Mechanize::Page]
|
|
245
245
|
# @return [Array<Relaton::Bib::Title>]
|
|
246
246
|
def fetch_title(doc)
|
|
247
|
-
t = doc.
|
|
247
|
+
t = doc.at_xpath("//hgroup/h2/text()|//hgroup/h1/text()")
|
|
248
248
|
unless t && !t.text.strip.empty?
|
|
249
249
|
@errors[:title] &&= true
|
|
250
250
|
return []
|
|
@@ -270,7 +270,7 @@ module Relaton
|
|
|
270
270
|
# @param doc [Mechanize::Page]
|
|
271
271
|
# @return [String]
|
|
272
272
|
def fetch_edition(doc)
|
|
273
|
-
ed = doc.
|
|
273
|
+
ed = doc.at_xpath("//h3[.='Edition:']/following-sibling::span")
|
|
274
274
|
@errors[:edition] &&= true
|
|
275
275
|
return unless ed
|
|
276
276
|
|
|
@@ -285,16 +285,16 @@ module Relaton
|
|
|
285
285
|
# @return [Array<Relaton::Cie::Relation>]
|
|
286
286
|
def fetch_relation(doc) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
287
287
|
rels = doc.xpath('//section[@class="history"]/ol/li[not(contains(@class,"selected-product"))]').map do |rel|
|
|
288
|
-
ref = rel.
|
|
288
|
+
ref = rel.at_xpath(".//a")
|
|
289
289
|
url = "https://www.techstreet.com#{ref[:href]}"
|
|
290
|
-
title = Bib::Title.from_string ref.
|
|
291
|
-
did = ref.
|
|
290
|
+
title = Bib::Title.from_string ref.at_xpath('.//p/span[@class="title"]').text
|
|
291
|
+
did = ref.at_xpath(".//h3").text
|
|
292
292
|
docid = [Bib::Docidentifier.new(type: "CIE", content: did, primary: true)]
|
|
293
|
-
on = ref.
|
|
293
|
+
on = ref.at_xpath(".//p/time")
|
|
294
294
|
date = [Bib::Date.new(type: "published", at: on[:datetime])]
|
|
295
295
|
source = [Bib::Uri.new(type: "src", content: url)]
|
|
296
296
|
bibitem = ItemData.new docidentifier: docid, title: title, source: source, date: date
|
|
297
|
-
type = ref.
|
|
297
|
+
type = ref.at_xpath('//li/i[contains(@class,"historical")]') ? "updates" : "updatedBy"
|
|
298
298
|
Bib::Relation.new(type: type, bibitem: bibitem)
|
|
299
299
|
end
|
|
300
300
|
@errors[:relation] &&= rels.empty?
|
|
@@ -313,7 +313,7 @@ module Relaton
|
|
|
313
313
|
# @param doc [Mechanize::Page]
|
|
314
314
|
# @return [Array<Relaton::Bib::LocalizedMarkedUpString>]
|
|
315
315
|
def fetch_abstract(doc)
|
|
316
|
-
content = doc.
|
|
316
|
+
content = doc.at_xpath('//div[contains(@class,"description")]')&.text&.strip
|
|
317
317
|
if content.nil? || content.empty?
|
|
318
318
|
@errors[:abstract] &&= true
|
|
319
319
|
return []
|
|
@@ -422,10 +422,10 @@ module Relaton
|
|
|
422
422
|
# workers. Called directly (single stubbed agent) by specs and from the
|
|
423
423
|
# pool with a per-worker agent/pacing/position.
|
|
424
424
|
#
|
|
425
|
-
# @param hit [
|
|
425
|
+
# @param hit [Moxml::Element]
|
|
426
426
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
427
427
|
def parse_page(hit, pos = nil, worker_agent = agent, pacing = nil)
|
|
428
|
-
url = hit.
|
|
428
|
+
url = hit.at_xpath('.//h3/a')[:href]
|
|
429
429
|
doc = time_req(pacing) { worker_agent.get url }
|
|
430
430
|
@mutex.synchronize do
|
|
431
431
|
item = ItemData.new(
|
|
@@ -460,7 +460,7 @@ module Relaton
|
|
|
460
460
|
def collect_hits(url = URL, hits = [])
|
|
461
461
|
result = time_req(listing_pacing) { agent.get url }
|
|
462
462
|
hits.concat result.xpath("//li[@data-product]").to_a
|
|
463
|
-
np = result.
|
|
463
|
+
np = result.at_xpath '//a[@class="next_page"]'
|
|
464
464
|
return hits unless np
|
|
465
465
|
|
|
466
466
|
next_href = np[:href]
|
data/lib/relaton/cie.rb
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/store"
|
|
4
|
+
|
|
5
|
+
module Relaton
|
|
6
|
+
# Access to LutaML data repositories through lutaml-store — the cloud
|
|
7
|
+
# store (api.relaton.org), a Pages/raw host, or a local GCR-style package,
|
|
8
|
+
# all through one read API with an explicit local package cache.
|
|
9
|
+
#
|
|
10
|
+
# This module composes lutaml-store sources; it is deliberately
|
|
11
|
+
# independent of Relaton::Db's own cache machinery (see
|
|
12
|
+
# relaton#204/#205 for that rework).
|
|
13
|
+
module Cloud
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# An explicitly configured source over a LutaML data repository.
|
|
17
|
+
#
|
|
18
|
+
# @param base_url [String] cloud store base (e.g. "https://api.relaton.org")
|
|
19
|
+
# @param collection [String] one collection (for relaton: a flavor)
|
|
20
|
+
# @param options [Hash] passthrough to Lutaml::Store::Source::Rest
|
|
21
|
+
# (headers:, timeout:, transport:, cache:, default_format:)
|
|
22
|
+
def source(base_url:, collection:, **options)
|
|
23
|
+
Lutaml::Store::Source.for(:rest, base_url: base_url, collection: collection, **options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# A local package directory source (a downloaded distribution or a
|
|
27
|
+
# Mirror/Repository pull).
|
|
28
|
+
def local(path)
|
|
29
|
+
Lutaml::Store::Source.for(:directory, path: path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The record's bytes. NotFoundError means the repository definitively
|
|
33
|
+
# does not have the key; BackendError means the network/service broke.
|
|
34
|
+
#
|
|
35
|
+
# @param key [String] the collection's storage key (see .resolve_key)
|
|
36
|
+
def read(key, source:)
|
|
37
|
+
source.read(key)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Resolves a reference to the collection's storage key. The contract's
|
|
41
|
+
# keys are URL-safe storage keys; slash-bearing docids live in the
|
|
42
|
+
# manifest's entries[].metadata.docid. Resolution is manifest-driven and
|
|
43
|
+
# explicit: exact storage-key match first, then the docid metadata,
|
|
44
|
+
# then nil — there is no path-encoding guesswork.
|
|
45
|
+
#
|
|
46
|
+
# @param ref [String] a storage key or a primary docidentifier
|
|
47
|
+
# @return [String, nil]
|
|
48
|
+
def resolve_key(ref, source:)
|
|
49
|
+
manifest = begin
|
|
50
|
+
source.manifest
|
|
51
|
+
rescue Lutaml::Store::NotFoundError
|
|
52
|
+
# a fresh local package holds no manifest yet — it holds no keys
|
|
53
|
+
return nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
return ref if manifest.key?(ref)
|
|
57
|
+
|
|
58
|
+
found = manifest.entries.find do |e|
|
|
59
|
+
e.metadata["docid"]&.casecmp?(ref)
|
|
60
|
+
end
|
|
61
|
+
found&.key
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The parsed record: the model class is declared by the caller — never
|
|
65
|
+
# inferred.
|
|
66
|
+
def get(key, model_class, source:)
|
|
67
|
+
source.get(key, model_class)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Reference in, parsed model out: resolves the reference through the
|
|
71
|
+
# manifest, reads the record (from the cloud source, or the local
|
|
72
|
+
# package when one is given), and deserializes with the caller's model
|
|
73
|
+
# class. The record's format is self-describing (Format.guess), so the
|
|
74
|
+
# same call serves YAML, JSON and XML records.
|
|
75
|
+
#
|
|
76
|
+
# @param ref [String] a storage key or a primary docidentifier
|
|
77
|
+
# @param model_class [Class] e.g. Relaton::Bib::Item (YAML/JSON
|
|
78
|
+
# records) or Relaton::Bib::Bibdata (XML bibdata records)
|
|
79
|
+
# @param cache [Lutaml::Store::Source::Directory, nil] read-through
|
|
80
|
+
# local package (writes through on a miss)
|
|
81
|
+
# @return the model instance
|
|
82
|
+
def fetch(ref, model_class, source:, cache: nil)
|
|
83
|
+
if cache
|
|
84
|
+
# Offline-first: resolve and read against the local package before
|
|
85
|
+
# touching the source — its manifest carries the same docid metadata.
|
|
86
|
+
local_key = resolve_key(ref, source: cache)
|
|
87
|
+
if local_key
|
|
88
|
+
return deserialize(cache.read(local_key), model_class)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
key = resolve_key(ref, source: source)
|
|
93
|
+
raise Lutaml::Store::NotFoundError, "unknown reference: #{ref.inspect}" unless key
|
|
94
|
+
|
|
95
|
+
body = if cache
|
|
96
|
+
Lutaml::Store::Repository.new(source: source, cache: cache).read(key)
|
|
97
|
+
else
|
|
98
|
+
source.read(key)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
deserialize(body, model_class)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def deserialize(body, model_class)
|
|
105
|
+
Lutaml::Store::Format.resolve(Lutaml::Store::Format.guess(body))
|
|
106
|
+
.deserialize(body, model_class)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Mirrors the whole collection into a GCR-style local package.
|
|
110
|
+
#
|
|
111
|
+
# @param into [String] target directory (a `<into>/<collection>` package
|
|
112
|
+
# is written)
|
|
113
|
+
# @return [Lutaml::Store::Source::Directory] a source over the package
|
|
114
|
+
def pull(source:, into:, collection: nil, force: false)
|
|
115
|
+
Lutaml::Store::Mirror.pull(source, into: into, collection: collection, force: force)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Read-through convenience: a Repository over a cloud source with an
|
|
119
|
+
# explicit local package cache; reads served from the cache once pulled.
|
|
120
|
+
def repository(base_url:, collection:, cache_path:, **options)
|
|
121
|
+
Lutaml::Store::Repository.new(
|
|
122
|
+
source: source(base_url: base_url, collection: collection, **options),
|
|
123
|
+
cache: local(cache_path)
|
|
124
|
+
)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require "forwardable"
|
|
2
|
-
require "
|
|
2
|
+
require "moxml"
|
|
3
3
|
require "weakref"
|
|
4
4
|
require_relative "hit"
|
|
5
5
|
|
|
@@ -51,21 +51,17 @@ module Relaton
|
|
|
51
51
|
# Renders the collection as XML
|
|
52
52
|
#
|
|
53
53
|
# @param opts [Hash] options
|
|
54
|
-
# @option opts [
|
|
54
|
+
# @option opts [Moxml::Builder] :builder XML builder
|
|
55
55
|
# @option opts [Boolean] :bibdata render bibdata if true
|
|
56
56
|
# @option opts [String, Symbol] :lang language
|
|
57
57
|
#
|
|
58
58
|
# @return [String] XML representation of the collection
|
|
59
59
|
#
|
|
60
60
|
def to_xml(**opts)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
builder.to_xml
|
|
61
|
+
parts = @array.map { |hit| hit.to_xml(**opts) }.join
|
|
62
|
+
Moxml.parse(
|
|
63
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<documents>#{parts}</documents>",
|
|
64
|
+
).to_xml(indent: 0, expand_empty: false)
|
|
69
65
|
end
|
|
70
66
|
|
|
71
67
|
#
|
|
@@ -32,6 +32,74 @@ module Relaton
|
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# The pubid identifier class `Relaton::Db` keys its cache with. Read from
|
|
36
|
+
# @pubid_identifier, else @pubid_flavor. @pubid_identifier exists so a
|
|
37
|
+
# flavor can key its cache with pubid without also sourcing #prefixes
|
|
38
|
+
# from pubid. Nil for a processor with neither: `Db` then keys its cache
|
|
39
|
+
# with the legacy string key.
|
|
40
|
+
#
|
|
41
|
+
# @return [Class, nil]
|
|
42
|
+
def pubid_class
|
|
43
|
+
flavor = @pubid_identifier || @pubid_flavor
|
|
44
|
+
return unless flavor
|
|
45
|
+
|
|
46
|
+
require "pubid"
|
|
47
|
+
@pubid_class ||= ::Pubid.const_get(flavor)::Identifier
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Parse a query reference into a pubid. A flavor that normalizes a
|
|
51
|
+
# reference before it parses overrides this. A parse error propagates:
|
|
52
|
+
# an unrecognized reference is not "not found".
|
|
53
|
+
#
|
|
54
|
+
# @param ref [String]
|
|
55
|
+
# @return [Pubid::Identifier, nil]
|
|
56
|
+
def cache_pubid(ref)
|
|
57
|
+
pubid_class&.parse(ref)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Set the year on a parsed pubid, where the flavor's `get` applies it. The
|
|
61
|
+
# default sets the identifier's own year. A flavor whose `get` applies
|
|
62
|
+
# the year elsewhere overrides this (see #fold_year_on_root).
|
|
63
|
+
#
|
|
64
|
+
# @param pubid [Pubid::Identifier]
|
|
65
|
+
# @param year [String, Integer]
|
|
66
|
+
# @return [Pubid::Identifier]
|
|
67
|
+
def fold_year(pubid, year)
|
|
68
|
+
pubid.class.from_hash pubid.to_hash.merge("year" => year.to_s)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Set the year on the document the identifier names (`#root`): the base
|
|
72
|
+
# document of a supplement, the adopted document of an adoption. For a
|
|
73
|
+
# flavor whose `get` applies the year there (ISO, CEN, BSI).
|
|
74
|
+
#
|
|
75
|
+
# @param pubid [Pubid::Identifier]
|
|
76
|
+
# @param year [String, Integer]
|
|
77
|
+
# @return [Pubid::Identifier]
|
|
78
|
+
def fold_year_on_root(pubid, year)
|
|
79
|
+
copy = pubid.class.from_hash pubid.to_hash
|
|
80
|
+
copy.root.date = ::Pubid::Components::Date.new(year: year.to_s)
|
|
81
|
+
copy
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The cache key for a query: the parsed pubid with the `year` and
|
|
85
|
+
# `all_parts` options folded in. The publication date range is not part
|
|
86
|
+
# of the identity, so it never enters the key.
|
|
87
|
+
#
|
|
88
|
+
# @param ref [String]
|
|
89
|
+
# @param year [String, Integer, nil]
|
|
90
|
+
# @param opts [Hash]
|
|
91
|
+
# @return [Pubid::Identifier, nil]
|
|
92
|
+
def cache_key(ref, year, opts)
|
|
93
|
+
pubid = cache_pubid(ref)
|
|
94
|
+
# A flavor that reads an unparseable reference as a miss (IANA, IEEE)
|
|
95
|
+
# gives nil or the raw String: no pubid key then.
|
|
96
|
+
return if pubid.nil? || !pubid.is_a?(::Pubid::Identifier)
|
|
97
|
+
|
|
98
|
+
pubid = fold_year(pubid, year) if year
|
|
99
|
+
pubid = pubid.to_all_parts if opts[:all_parts] && !pubid.all_parts?
|
|
100
|
+
pubid
|
|
101
|
+
end
|
|
102
|
+
|
|
35
103
|
def initialize
|
|
36
104
|
raise "This is an abstract class!"
|
|
37
105
|
end
|