relaton 3.0.0.pre.alpha.13 → 3.0.0.pre.alpha.15

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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/lib/relaton/3gpp/bibliography.rb +7 -2
  3. data/lib/relaton/3gpp.rb +4 -0
  4. data/lib/relaton/adobe/bibliography.rb +5 -2
  5. data/lib/relaton/adobe.rb +4 -0
  6. data/lib/relaton/bib/model/item_shared.rb +5 -1
  7. data/lib/relaton/bib/model/localized_string.rb +10 -0
  8. data/lib/relaton/bipm/bibliography.rb +5 -1
  9. data/lib/relaton/bipm.rb +4 -0
  10. data/lib/relaton/calconnect/hit_collection.rb +7 -2
  11. data/lib/relaton/calconnect.rb +4 -0
  12. data/lib/relaton/ccsds/hit_collection.rb +5 -1
  13. data/lib/relaton/ccsds.rb +4 -0
  14. data/lib/relaton/cie/scrapper.rb +5 -1
  15. data/lib/relaton/cie.rb +4 -0
  16. data/lib/relaton/db/cache.rb +10 -5
  17. data/lib/relaton/db/registry.rb +1 -1
  18. data/lib/relaton/easc/bibliography.rb +5 -2
  19. data/lib/relaton/easc.rb +4 -0
  20. data/lib/relaton/ecma/bibliography.rb +5 -1
  21. data/lib/relaton/ecma.rb +4 -0
  22. data/lib/relaton/etsi/bibliography.rb +5 -1
  23. data/lib/relaton/etsi.rb +4 -0
  24. data/lib/relaton/evs/bibliography.rb +138 -0
  25. data/lib/relaton/evs/hit.rb +32 -0
  26. data/lib/relaton/evs/hit_collection.rb +73 -0
  27. data/lib/relaton/evs/item_data.rb +7 -0
  28. data/lib/relaton/evs/model/bibdata.rb +8 -0
  29. data/lib/relaton/evs/model/bibitem.rb +8 -0
  30. data/lib/relaton/evs/model/docidentifier.rb +79 -0
  31. data/lib/relaton/evs/model/ext.rb +12 -0
  32. data/lib/relaton/evs/model/item.rb +14 -0
  33. data/lib/relaton/evs/model/structured_identifier.rb +9 -0
  34. data/lib/relaton/evs/processor.rb +50 -0
  35. data/lib/relaton/evs/scraper.rb +212 -0
  36. data/lib/relaton/evs/util.rb +8 -0
  37. data/lib/relaton/evs.rb +27 -0
  38. data/lib/relaton/gost/bibliography.rb +5 -2
  39. data/lib/relaton/gost.rb +4 -0
  40. data/lib/relaton/iala/bibliography.rb +5 -2
  41. data/lib/relaton/iala.rb +4 -0
  42. data/lib/relaton/iana/bibliography.rb +5 -2
  43. data/lib/relaton/iana.rb +4 -0
  44. data/lib/relaton/iec/hit_collection.rb +7 -2
  45. data/lib/relaton/iec.rb +4 -0
  46. data/lib/relaton/ieee/bibliography.rb +14 -2
  47. data/lib/relaton/ieee.rb +4 -0
  48. data/lib/relaton/ietf/scraper.rb +7 -2
  49. data/lib/relaton/ietf.rb +4 -0
  50. data/lib/relaton/iho/bibliography.rb +5 -2
  51. data/lib/relaton/iho/processor.rb +3 -0
  52. data/lib/relaton/iho.rb +4 -0
  53. data/lib/relaton/iso/hit_collection.rb +7 -2
  54. data/lib/relaton/iso.rb +4 -0
  55. data/lib/relaton/itu/hit_collection.rb +7 -2
  56. data/lib/relaton/itu/model/ext.rb +10 -0
  57. data/lib/relaton/itu.rb +4 -0
  58. data/lib/relaton/jcgm/bibliography.rb +5 -2
  59. data/lib/relaton/jcgm.rb +4 -0
  60. data/lib/relaton/nist/hit_collection.rb +20 -1
  61. data/lib/relaton/oasis/bibliography.rb +5 -2
  62. data/lib/relaton/oasis.rb +4 -0
  63. data/lib/relaton/ogc/hit_collection.rb +7 -2
  64. data/lib/relaton/ogc.rb +4 -0
  65. data/lib/relaton/oiml/bibliography.rb +5 -2
  66. data/lib/relaton/oiml.rb +4 -0
  67. data/lib/relaton/omg/bibliography.rb +40 -2
  68. data/lib/relaton/omg/scraper.rb +14 -2
  69. data/lib/relaton/omg.rb +11 -0
  70. data/lib/relaton/version.rb +1 -1
  71. data/lib/relaton/xsf/hit_collection.rb +5 -2
  72. data/lib/relaton/xsf.rb +4 -0
  73. data/lib/relaton.rb +1 -0
  74. metadata +20 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69f8e17f2c0ce9465b7cc3b648507007c8d0f1efd6d1005370aebcf7f6d10526
4
- data.tar.gz: 9d020e7b59a8e08876262d9079df95dfa781954cd8583c32a19ea2c85c660b53
3
+ metadata.gz: e2c8ca4e0641e619f634a850d3a4001e2077e9cff815329d69e1497c46f323ed
4
+ data.tar.gz: 47cc2a2852bfa5348229f6e63b7ff08195f2b0b5a558ea1d563a03f768c15ba3
5
5
  SHA512:
6
- metadata.gz: 1d4121f0ec164ba41a1ff23dfa51dc7c995bf8a90852ad8ca27ac6221fa5547172997561c9e2790996cb9405c89b7662cfdabf88de07bcf3a43011213b9f7de3
7
- data.tar.gz: f2bad59aaae815eca687007cbc34d6920c9ca4706e67b50e912bf40579f27f17b4e658fce81923a2899ae74c127e85788fd28518cb12d4233bd4053364ae6ca5
6
+ metadata.gz: 5ebecc67f9bba5b19b363ac653283701f1ff887a08b6de119adfd7f7847d694899227d058fe362b3a28cd479f319edb7b805ad702ee9b7a9b3c077ae42ce2b15
7
+ data.tar.gz: e491a0b7e33fc1f38280891933465391225d0710a24b62bb732e5f7414ca037d5ab65f3d83f907323636c1535ecfd2a35d9b74d76bfe65ebf7f3f171d0665adf
@@ -123,10 +123,15 @@ module Relaton
123
123
  # deserialize the rows into identifiers, sort them by `id.root.number`,
124
124
  # and let `Type#search` bsearch. Without it the rows stay raw hashes,
125
125
  # `FileIO#sorted` stays false, and every lookup scans all 88,464 of them.
126
+ #
127
+ # The machine index on the Pages site (relaton#206, the W3C pilot's
128
+ # contract): a parsed query reads only its own shard. A Pages failure
129
+ # raises `Relaton::RequestError`; the whole-index zip on the same site
130
+ # serves only the retry on a miss.
131
+ #
126
132
  def index
127
133
  Relaton::Index.find_or_create(
128
- "3GPP", url: "#{SOURCE}#{INDEXFILE}.zip", file: "#{INDEXFILE}.yaml",
129
- pubid_class: ::Pubid::Tgpp::Identifier
134
+ "3GPP", pages_url: PAGES_URL, pubid_class: ::Pubid::Tgpp::Identifier
130
135
  )
131
136
  end
132
137
 
data/lib/relaton/3gpp.rb CHANGED
@@ -16,6 +16,10 @@ require_relative "3gpp/bibliography"
16
16
  module Relaton
17
17
  module ThreeGpp
18
18
  INDEXFILE = "index-v2".freeze
19
+ # The machine index on the data repo's Pages site (relaton#206) —
20
+ # manifest + per-shard rows read by Bibliography's index; the zip stays
21
+ # on the Pages site for the whole-index retry on a miss.
22
+ PAGES_URL = "https://relaton.github.io/relaton-data-3gpp/"
19
23
 
20
24
  # Returns hash of XML reammar
21
25
  # @return [String]
@@ -70,10 +70,13 @@ module Relaton
70
70
  end
71
71
 
72
72
  def index
73
+ # The machine index on the Pages site (relaton#206): a parsed query
74
+ # reads only its own shard; a Pages failure raises
75
+ # `Relaton::RequestError`; the whole-index zip on the same site
76
+ # serves only the retry on a miss.
73
77
  Relaton::Index.find_or_create(
74
78
  :adobe,
75
- url: "#{ENDPOINT}#{INDEXFILE}.zip",
76
- file: "#{INDEXFILE}.yaml",
79
+ pages_url: PAGES_URL,
77
80
  pubid_class: ::Pubid::Adobe::Identifier,
78
81
  )
79
82
  end
data/lib/relaton/adobe.rb CHANGED
@@ -13,6 +13,10 @@ require "relaton/version"
13
13
  module Relaton
14
14
  module Adobe
15
15
  INDEXFILE = "index-v2".freeze
16
+ # The machine index on the data repo's Pages site (relaton#206) —
17
+ # manifest + per-shard rows read by the consumer's index; the zip stays
18
+ # on the Pages site for the whole-index retry on a miss.
19
+ PAGES_URL = "https://relaton.github.io/relaton-data-adobe/"
16
20
 
17
21
  class Error < StandardError; end
18
22
 
@@ -12,7 +12,11 @@ module Relaton
12
12
  graphic_work music patent inbook incollection inproceedings journal website
13
13
  webresource dataset archival social_media alert message convesation misc
14
14
  ]
15
- attribute :formattedref, Formattedref
15
+ # Lenient ingest: some upstream corpus documents carry more than one
16
+ # <formattedref> in a bibitem; keep the first instead of failing the
17
+ # whole ingest (lutaml-model#971, the singular_tolerance option in
18
+ # 0.8.112).
19
+ attribute :formattedref, Formattedref, singular_tolerance: :first
16
20
  attribute :title, Title, collection: true, initialize_empty: true
17
21
  attribute :source, Uri, collection: true, initialize_empty: true
18
22
  attribute :docidentifier, Docidentifier, collection: true, initialize_empty: true
@@ -32,6 +32,16 @@ module Relaton
32
32
  end
33
33
  end
34
34
 
35
+ # Cast at the assignment layer (lutaml-model#972): a bare scalar
36
+ # assigned to a singular custom-typed attribute (e.g. a relation's
37
+ # description) is wrapped here, so the serializer's type validation
38
+ # sees an instance instead of raising IncorrectModelError.
39
+ def self.cast(value)
40
+ return value if value.is_a?(self)
41
+
42
+ new(content: value.to_s)
43
+ end
44
+
35
45
  attribute :content, :string
36
46
  prepend ContentSanitization
37
47
 
@@ -215,8 +215,12 @@ module Relaton::Bipm
215
215
  end
216
216
 
217
217
  def index
218
+ # The machine index on the Pages site (relaton#206): a parsed query
219
+ # reads only its own shard; a Pages failure raises
220
+ # `Relaton::RequestError`; the whole-index zip on the same site
221
+ # serves only the retry on a miss.
218
222
  Relaton::Index.find_or_create(
219
- :bipm, url: "#{GH_ENDPOINT}#{INDEXFILE}.zip", file: "#{INDEXFILE}.yaml",
223
+ :bipm, pages_url: PAGES_URL,
220
224
  pubid_class: ::Pubid::Bipm::Identifier
221
225
  )
222
226
  end
data/lib/relaton/bipm.rb CHANGED
@@ -23,6 +23,10 @@ module Relaton
23
23
  # by `relaton-data-bipm`'s crawler (using the retained `Relaton::Bipm::Id`),
24
24
  # but this flavor no longer reads or writes it.
25
25
  INDEXFILE = "index-v2".freeze
26
+ # The machine index on the data repo's Pages site (relaton#206) —
27
+ # manifest + per-shard rows read by the consumer's index; the zip stays
28
+ # on the Pages site for the whole-index retry on a miss.
29
+ PAGES_URL = "https://relaton.github.io/relaton-data-bipm/"
26
30
 
27
31
  # Returns hash of gems versions used to generate the data model.
28
32
  # @return [String]
@@ -12,9 +12,14 @@ module Relaton::Calconnect
12
12
  private
13
13
 
14
14
  # @return [Relaton::Index::Type]
15
+ #
16
+ # The machine index on the Pages site (relaton#206, the W3C pilot's
17
+ # contract): a parsed query reads only its own shard. A Pages failure
18
+ # raises `Relaton::RequestError`; the whole-index zip on the same site
19
+ # serves only the retry on a miss.
20
+ #
15
21
  def index
16
- Relaton::Index.find_or_create :CC, url: "#{GHURL}#{INDEXFILE}.zip",
17
- file: "#{INDEXFILE}.yaml",
22
+ Relaton::Index.find_or_create :CC, pages_url: PAGES_URL,
18
23
  pubid_class: ::Pubid::Calconnect::Identifier
19
24
  end
20
25
 
@@ -23,6 +23,10 @@ module Relaton
23
23
  # `relaton-data-calconnect`'s crawler derives the legacy `index-v1` from
24
24
  # these rows for released consumers, so it is not produced or read here.
25
25
  INDEXFILE = "index-v2".freeze
26
+ # The machine index on the data repo's Pages site (relaton#206) —
27
+ # manifest + per-shard rows read by HitCollection's index; the zip stays
28
+ # on the Pages site for the whole-index retry on a miss.
29
+ PAGES_URL = "https://relaton.github.io/relaton-data-calconnect/"
26
30
 
27
31
  # Returns hash of XML reammar
28
32
  # @return [String]
@@ -20,8 +20,12 @@ module Relaton
20
20
  # Pubid index (index-v2): `:id` deserializes to a Pubid::Ccsds::Identifier
21
21
  # via pubid_class, so search narrows by number with binary search.
22
22
  def index
23
+ # The machine index on the Pages site (relaton#206): a parsed query
24
+ # reads only its own shard; a Pages failure raises
25
+ # `Relaton::RequestError`; the whole-index zip on the same site
26
+ # serves only the retry on a miss.
23
27
  @index ||= Relaton::Index.find_or_create(
24
- :ccsds, url: "#{GHURL}#{INDEXFILE}.zip", file: "#{INDEXFILE}.yaml", pubid_class: Pubid::Ccsds::Identifier
28
+ :ccsds, pages_url: PAGES_URL, pubid_class: Pubid::Ccsds::Identifier
25
29
  )
26
30
  end
27
31
 
data/lib/relaton/ccsds.rb CHANGED
@@ -19,6 +19,10 @@ module Relaton
19
19
  # index-v1 (pubid-v1 hash, for the released gem line) is rebuilt separately
20
20
  # by the data repo's build_index_v1.rb.
21
21
  INDEXFILE = "index-v2"
22
+ # The machine index on the data repo's Pages site (relaton#206) —
23
+ # manifest + per-shard rows read by the consumer's index; the zip stays
24
+ # on the Pages site for the whole-index retry on a miss.
25
+ PAGES_URL = "https://relaton.github.io/relaton-data-ccsds/"
22
26
 
23
27
  class Error < StandardError; end
24
28
  # Your code goes here...
@@ -15,7 +15,11 @@ module Relaton
15
15
  # "... is not a recognized standards identifier". Partial refs
16
16
  # (`CIE 001`, `CIE 15`) parse, so nothing valid is lost.
17
17
  pubid = to_pubid ref
18
- index = Index.find_or_create :cie, url: "#{ENDPOINT}#{INDEXFILE}.zip", file: "#{INDEXFILE}.yaml",
18
+ # The machine index on the Pages site (relaton#206): a parsed query
19
+ # reads only its own shard; a Pages failure raises
20
+ # `Relaton::RequestError`; the whole-index zip on the same site
21
+ # serves only the retry on a miss.
22
+ index = Index.find_or_create :cie, pages_url: PAGES_URL,
19
23
  pubid_class: ::Pubid::Cie::Identifier
20
24
  # Pass the parsed pubid (not the raw String) so index-v2 narrows
21
25
  # candidates by number via binary search before the block runs; the
data/lib/relaton/cie.rb CHANGED
@@ -16,6 +16,10 @@ require_relative "cie/scrapper"
16
16
  module Relaton
17
17
  module Cie
18
18
  INDEXFILE = "index-v2".freeze
19
+ # The machine index on the data repo's Pages site (relaton#206) —
20
+ # manifest + per-shard rows read by the consumer's index; the zip stays
21
+ # on the Pages site for the whole-index retry on a miss.
22
+ PAGES_URL = "https://relaton.github.io/relaton-data-cie/"
19
23
 
20
24
  # Returns hash of XML reammar
21
25
  # @return [String]
@@ -3,7 +3,6 @@ require "digest"
3
3
  require "json"
4
4
  require "date"
5
5
  require "lutaml/store"
6
- require "nokogiri"
7
6
  require_relative "cache_entry"
8
7
 
9
8
  module Relaton
@@ -335,12 +334,18 @@ module Relaton
335
334
 
336
335
  # The wrapped string key of an old-layout document: its own primary
337
336
  # docidentifier under its flavor prefix, resolved through the same
338
- # `wrapped_key` path a string cache read takes.
337
+ # `wrapped_key` path a string cache read takes. A value no parser reads
338
+ # is no document — the legacy cache predates this gem's own writes —
339
+ # not a crash.
339
340
  def old_layout_key(path, value)
340
341
  flavor = path.split("/").first or return
341
- doc = Nokogiri::XML(value)
342
- id = doc.root&.at("./docidentifier[@primary = 'true']") ||
343
- doc.root&.at("./docidentifier") or return
342
+ root = begin
343
+ Moxml.new.parse(value, readonly: true).root
344
+ rescue Moxml::ParseError
345
+ nil
346
+ end or return
347
+ id = root.at_xpath("./docidentifier[@primary = 'true']") ||
348
+ root.at_xpath("./docidentifier") or return
344
349
 
345
350
  "#{flavor.upcase}(#{id.text.strip})"
346
351
  end
@@ -13,7 +13,7 @@ module Relaton
13
13
  relaton/oasis relaton/doi relaton/jis relaton/xsf
14
14
  relaton/ccsds relaton/etsi relaton/isbn relaton/plateau
15
15
  relaton/oiml relaton/jcgm relaton/easc relaton/gost relaton/adobe
16
- relaton/iala
16
+ relaton/iala relaton/evs
17
17
  ].freeze
18
18
 
19
19
  include Singleton
@@ -68,10 +68,13 @@ module Relaton
68
68
  private
69
69
 
70
70
  def index
71
+ # The machine index on the Pages site (relaton#206): a parsed query
72
+ # reads only its own shard; a Pages failure raises
73
+ # `Relaton::RequestError`; the whole-index zip on the same site
74
+ # serves only the retry on a miss.
71
75
  Relaton::Index.find_or_create(
72
76
  :easc,
73
- url: "#{ENDPOINT}#{INDEXFILE}.zip",
74
- file: "#{INDEXFILE}.yaml",
77
+ pages_url: PAGES_URL,
75
78
  pubid_class: ::Pubid::Easc::Identifier,
76
79
  )
77
80
  end
data/lib/relaton/easc.rb CHANGED
@@ -13,6 +13,10 @@ require "pubid"
13
13
  module Relaton
14
14
  module Easc
15
15
  INDEXFILE = "index-v2".freeze
16
+ # The machine index on the data repo's Pages site (relaton#206) —
17
+ # manifest + per-shard rows read by the consumer's index; the zip stays
18
+ # on the Pages site for the whole-index retry on a miss.
19
+ PAGES_URL = "https://relaton.github.io/relaton-data-easc/"
16
20
 
17
21
  class Error < StandardError; end
18
22
 
@@ -11,8 +11,12 @@ module Relaton
11
11
  class << self
12
12
  # @return [Relaton::Index::Type]
13
13
  def index
14
+ # The machine index on the Pages site (relaton#206): a parsed query
15
+ # reads only its own shard; a Pages failure raises
16
+ # `Relaton::RequestError`; the whole-index zip on the same site
17
+ # serves only the retry on a miss.
14
18
  Relaton::Index.find_or_create(
15
- :ECMA, url: "#{ENDPOINT}#{INDEXFILE}.zip", file: "#{INDEXFILE}.yaml",
19
+ :ECMA, pages_url: PAGES_URL,
16
20
  pubid_class: ::Pubid::Ecma::Identifier
17
21
  )
18
22
  end
data/lib/relaton/ecma.rb CHANGED
@@ -20,6 +20,10 @@ module Relaton
20
20
  # `relaton-data-ecma`'s crawler derives the legacy `index-v1` from these
21
21
  # rows for released consumers, so it is not produced or read here.
22
22
  INDEXFILE = "index-v2".freeze
23
+ # The machine index on the data repo's Pages site (relaton#206) —
24
+ # manifest + per-shard rows read by the consumer's index; the zip stays
25
+ # on the Pages site for the whole-index retry on a miss.
26
+ PAGES_URL = "https://relaton.github.io/relaton-data-ecma/"
23
27
 
24
28
  # Returns hash of XML reammar
25
29
  # @return [String]
@@ -16,7 +16,11 @@ module Relaton
16
16
  # A parsed pubid comes from Relaton::Db (relaton#205); it is only read.
17
17
  pubid = ref.is_a?(String) ? ::Pubid::Etsi.parse(ref) : ref
18
18
 
19
- index = Relaton::Index.find_or_create :etsi, url: "#{SOURCE}#{INDEXFILE}.zip", file: "#{INDEXFILE}.yaml",
19
+ # The machine index on the Pages site (relaton#206): a parsed query
20
+ # reads only its own shard; a Pages failure raises
21
+ # `Relaton::RequestError`; the whole-index zip on the same site
22
+ # serves only the retry on a miss.
23
+ index = Relaton::Index.find_or_create :etsi, pages_url: PAGES_URL,
20
24
  pubid_class: ::Pubid::Etsi::Identifier
21
25
  row = best_match(index, pubid)
22
26
  return unless row
data/lib/relaton/etsi.rb CHANGED
@@ -14,6 +14,10 @@ require_relative "etsi/bibliography"
14
14
  module Relaton
15
15
  module Etsi
16
16
  INDEXFILE = "index-v2".freeze
17
+ # The machine index on the data repo's Pages site (relaton#206) —
18
+ # manifest + per-shard rows read by the consumer's index; the zip stays
19
+ # on the Pages site for the whole-index retry on a miss.
20
+ PAGES_URL = "https://relaton.github.io/relaton-data-etsi/"
17
21
 
18
22
  # Returns hash of gem versions used to generate data model.
19
23
  # @return [String]
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Evs
5
+ # Class methods for search Estonian standards.
6
+ class Bibliography
7
+ class << self
8
+ # @param ref [String] the printed reference
9
+ # @return [Relaton::Evs::HitCollection]
10
+ def search(ref, year = nil)
11
+ HitCollection.new(ref, year).search
12
+ rescue Net::ReadTimeout, Net::OpenTimeout => e
13
+ raise Relaton::RequestError, e.message
14
+ end
15
+
16
+ #
17
+ # Parses a printed EVS reference.
18
+ #
19
+ # Deliberately does NOT rescue. An unrecognized query reference must
20
+ # raise, so that relaton-cli can print `"…" is not a recognized
21
+ # standards identifier`; returning nil would make "malformed" look
22
+ # like "not found". Only the DATA side rescues — see `Hit#pubid`.
23
+ #
24
+ # @param ref [String] a printed reference, e.g. "EVS-EN 18216:2026"
25
+ #
26
+ # @return [Pubid::Evs::Identifier]
27
+ # @raise [Pubid::Errors::ParseError]
28
+ #
29
+ def parse(ref)
30
+ ::Pubid::Evs.parse ref
31
+ end
32
+
33
+ #
34
+ # @param ref [String, Pubid::Evs::Identifier] the EVS standard
35
+ # reference to look up, or its parse from Relaton::Db (relaton#205)
36
+ # @param year [String] the year the standard was published (optional)
37
+ # @param opts [Hash] options
38
+ # @option opts [Boolean] :keep_year don't update reference
39
+ #
40
+ # @return [Relaton::Evs::ItemData, nil]
41
+ #
42
+ def get(ref, year = nil, opts = {})
43
+ # An empty string is no reference, not a malformed one, so it stays a
44
+ # plain nil rather than a parse failure. Mirrors the guard
45
+ # HitCollection#search already carries.
46
+ return if ref.nil? || (ref.is_a?(String) && ref.strip.empty?)
47
+
48
+ # `Relaton::Db` hands `get` the parse it routed and keyed the cache
49
+ # with; it must reach `bib_get` unmutated.
50
+ query = ref.is_a?(String) ? parse(ref) : ref
51
+ year ||= publication_year query
52
+
53
+ bib_get ref.to_s, year, opts, query
54
+ end
55
+
56
+ private
57
+
58
+ #
59
+ # `#root` is the accessor that answers for every form: a national
60
+ # adoption keeps the origin standard's number and year behind its
61
+ # `base` chain, and a direct adoption or plain Estonian standard is
62
+ # its own root.
63
+ #
64
+ # @param id [Pubid::Evs::Identifier]
65
+ #
66
+ # @return [String, nil]
67
+ #
68
+ def publication_year(id)
69
+ id.root.year&.to_s
70
+ end
71
+
72
+ # A component is absent from a reference when excluding it changes
73
+ # nothing.
74
+ #
75
+ # @return [Boolean]
76
+ def absent?(id, *keys)
77
+ id.exclude(*keys) == id
78
+ end
79
+
80
+ def fetch_ref_err(ref, year, missed_years)
81
+ unless missed_years.empty?
82
+ Util.info "There was no match for `#{year}`, though there " \
83
+ "were matches found for `#{missed_years.join('`, `')}`.",
84
+ key: ref
85
+ end
86
+ nil
87
+ end
88
+
89
+ #
90
+ # Selects the catalogue hits that denote the same document as the
91
+ # query. The selection is pubid's subset match `query === hit.pubid`:
92
+ # a year the query omits matches any value, and the class must be
93
+ # identical, so a base reference never answers with its own
94
+ # amendment's record.
95
+ #
96
+ # @param ref [String] the printed reference, as the catalogue URL
97
+ # wants it
98
+ # @param query [Pubid::Evs::Identifier]
99
+ #
100
+ # @return [Relaton::Evs::HitCollection]
101
+ #
102
+ def search_filter(ref, query)
103
+ search(ref).select! { |hit| hit.pubid && query === hit.pubid }
104
+ end
105
+
106
+ # Fetch the matching hit, keeping the year filter for error reporting.
107
+ def isobib_results_filter(result, year)
108
+ missed_years = []
109
+ result.each do |r|
110
+ pyear = r.pubid && publication_year(r.pubid)
111
+ if !year || year == pyear
112
+ ret = r.item
113
+ return { ret: ret } if ret
114
+ end
115
+
116
+ missed_years << pyear
117
+ end
118
+ { years: missed_years }
119
+ end
120
+
121
+ def bib_get(ref, year, opts, query)
122
+ key = year && absent?(query, :year) ? "#{ref}:#{year}" : ref
123
+ Util.info "Fetching from www.evs.ee ...", key: key
124
+ result = search_filter(ref, query)
125
+ ret = isobib_results_filter(result, year)
126
+ if ret[:ret]
127
+ bib = year || opts[:keep_year] ? ret[:ret] : ret[:ret].to_most_recent_reference
128
+ Util.info "Found: `#{bib.docidentifier.first&.content}`", key: key
129
+ bib
130
+ else
131
+ Util.info "Not found.", key: key
132
+ fetch_ref_err(ref, year, ret[:years])
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Evs
5
+ # Hit.
6
+ class Hit < Relaton::Core::Hit
7
+ attr_writer :item
8
+
9
+ # Parse page.
10
+ # @return [Relaton::Evs::ItemData]
11
+ def item
12
+ @fetch ||= Scraper.parse_page self
13
+ end
14
+
15
+ # The hit's code parsed with pubid, memoized because the filter, the
16
+ # sort and the scraper all read it. `nil` when pubid cannot parse the
17
+ # code: the catalogue also hosts documents outside the EVS grammar
18
+ # (a bare `EN` or `ISO` record linked as a base document), and one such
19
+ # row must not abort the search. (A malformed *query* raises; see
20
+ # `Bibliography.parse`.)
21
+ #
22
+ # @return [Pubid::Evs::Identifier, nil]
23
+ def pubid
24
+ return @pubid if defined?(@pubid)
25
+
26
+ @pubid = Bibliography.parse hit[:code]
27
+ rescue StandardError
28
+ @pubid = nil
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Relaton
4
+ module Evs
5
+ # Page of hit collection.
6
+ class HitCollection < Relaton::Core::HitCollection
7
+ DOMAIN = "https://www.evs.ee"
8
+
9
+ def search
10
+ @array = hits
11
+ self
12
+ end
13
+
14
+ def agent
15
+ @agent ||= Mechanize.new.tap { |a| a.user_agent_alias = "Mac Safari" }
16
+ end
17
+
18
+ def select!(&block)
19
+ @array.select!(&block)
20
+ self
21
+ end
22
+
23
+ private
24
+
25
+ #
26
+ # The catalogue URL is the printed reference slugified
27
+ # (`EVS-EN 18216:2026` -> /en/evs-en-18216-2026), so the search is a
28
+ # direct probe: the product page exists iff the reference denotes a
29
+ # published document. A miss redirects to /page-not-found, which is no
30
+ # hit. A reference carrying the `et` language code probes the Estonian
31
+ # locale, which serves the Estonian-language product.
32
+ #
33
+ # @return [Array<Relaton::Evs::Hit>]
34
+ #
35
+ def hits
36
+ page = agent.get url
37
+ return [] if page.uri.to_s.include? "page-not-found"
38
+
39
+ [Hit.new({ code: code, url: page.uri.to_s }, self)]
40
+ rescue Mechanize::ResponseCodeError
41
+ []
42
+ end
43
+
44
+ def url
45
+ "#{DOMAIN}/#{locale}/#{slug}"
46
+ end
47
+
48
+ def locale
49
+ query_pubid&.language == "et" ? "et" : "en"
50
+ end
51
+
52
+ # The hit's code is the reference as queried, minus the "(all parts)"
53
+ # marker: `Hit#pubid` parses it back, and the filter `query === hit.pubid`
54
+ # needs the language code ON the hit for a language-carrying query to
55
+ # match its own record. The slug derivation strips the language code
56
+ # separately — the catalogue URL does not carry it.
57
+ def code
58
+ ref.to_s.sub(/\s+\(all parts\)\z/, "")
59
+ end
60
+
61
+ def slug
62
+ code.sub(/\s+(et-en|et|en)\z/, "")
63
+ .downcase.gsub(/[^a-z0-9]+/, "-").gsub(/\A-+|-+\z/, "")
64
+ end
65
+
66
+ def query_pubid
67
+ Bibliography.parse code
68
+ rescue StandardError
69
+ nil
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,7 @@
1
+ module Relaton
2
+ module Evs
3
+ # Item data class.
4
+ class ItemData < Bib::ItemData
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Relaton
2
+ module Evs
3
+ class Bibdata < Item
4
+ model ItemData
5
+ include Bib::BibdataShared
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Relaton
2
+ module Evs
3
+ class Bibitem < Item
4
+ model ItemData
5
+ include Bib::BibitemShared
6
+ end
7
+ end
8
+ end