relaton-iho 2.0.0 → 2.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 645f3bd9a36b81f542661a5b85d0895c13944c0f1fbd8468430b8b2df4c06778
4
- data.tar.gz: 2dd9e5fa31a8dcbc1cc97e24ad4ed9c6b73b7d2e68acecc02b13666b6cfd9d5b
3
+ metadata.gz: a104bbd46d4c16a491e1ee286734f2cf49cfcd2918a7be600a300e496f58dd99
4
+ data.tar.gz: c66076c924c1ed5af548ea3cda7906152a59df7504e652cb82cb079a0226251b
5
5
  SHA512:
6
- metadata.gz: 65b898a46d168e94713c545d7698dfabf18075d57ffcb0fb29f248270c63e5c9aa43e513890226d71b2b613520b75c95e46f20547986c9e3c21d1862c4d3012d
7
- data.tar.gz: 2a2765bdb4e7fcd04f5da7cf7efdd8b70e8acd12a46c46f373a64b9f9dd50a024f2e3aa854bce6c9e3c55b4de00c7ec78758073dae5cba350f8e9beb72e701ba
6
+ metadata.gz: f5369e5fcf09ec8b78e8f7a080e6996c925ba9f521e371ddde7f915b5823e1491069006c7a61a8e86e794ceb44d75f64b100c8476a0c26f114845be78513a4b2
7
+ data.tar.gz: b76e65c399900c37cc8d3dcb4d99f4ffed1964f371ce5b9f213246f21f62df0eca96d6828aee25be02f6b17eafa7d0232313791c1176c632f355f400a8513bc0
data/CLAUDE.md CHANGED
@@ -64,3 +64,7 @@ In `item.rb`, `require_relative "relation"` is placed **after** the `Item` class
64
64
  ### IHO-Specific Document Types
65
65
 
66
66
  policy-and-procedures, best-practices, supporting-document, report, legal, directives, proposal, standard
67
+
68
+ ## Testing
69
+
70
+ - **Index fixture:** `spec/fixtures/index-v1.zip` is pre-loaded into `Relaton::Index` pool in `before(:suite)` (configured in `spec/support/webmock.rb`). Run `rake spec:update_index` to refresh from relaton-data-iho.
data/Gemfile CHANGED
@@ -5,6 +5,7 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in relaton_iho.gemspec
6
6
  gemspec
7
7
 
8
+
8
9
  gem "byebug"
9
10
  gem "equivalent-xml", "~> 0.6"
10
11
  gem "rake", "~> 13.0"
data/README.adoc CHANGED
@@ -137,6 +137,8 @@ RelatonIho uses the relaton-logger gem for logging. By default, it logs to STDOU
137
137
 
138
138
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
139
139
 
140
+ To update the index test fixture (used by tests), run `rake spec:update_index`. This downloads the latest `index-v1.zip` from the https://github.com/relaton/relaton-data-iho[relaton-data-iho] repository.
141
+
140
142
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to https://rubygems.org[rubygems.org].
141
143
 
142
144
  == Contributing
data/Rakefile CHANGED
@@ -4,3 +4,27 @@ require "rspec/core/rake_task"
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
6
  task :default => :spec
7
+
8
+ namespace :spec do
9
+ desc "Download latest IHO index fixture from relaton-data-iho"
10
+ task :update_index do
11
+ require "net/http"
12
+ require "uri"
13
+ require_relative "lib/relaton/iho"
14
+
15
+ filename = "#{Relaton::Iho::INDEXFILE}.zip"
16
+ url = "https://raw.githubusercontent.com/relaton/relaton-data-iho/data-v2/\#{filename}"
17
+ dest = File.join(__dir__, "spec", "fixtures", filename)
18
+
19
+ puts "Downloading \#{url} ..."
20
+ uri = URI.parse(url)
21
+ response = Net::HTTP.get_response(uri)
22
+
23
+ if response.is_a?(Net::HTTPSuccess)
24
+ File.binwrite(dest, response.body)
25
+ puts "Updated \#{dest} (\#{response.body.bytesize} bytes)"
26
+ else
27
+ abort "Failed to download: HTTP \#{response.code}"
28
+ end
29
+ end
30
+ end
data/grammars/biblio.rng CHANGED
@@ -2015,15 +2015,11 @@ provided that it is not the entire bibliographic item that is so related</a:docu
2015
2015
  <a:documentation>A version of the bibliographic item (within an edition). Can be used for drafts</a:documentation>
2016
2016
  <element name="version">
2017
2017
  <optional>
2018
- <ref name="revision-date">
2019
- <a:documentation>The date at which the current version of the bibliographic item was produced</a:documentation>
2020
- </ref>
2021
- </optional>
2022
- <optional>
2023
- <ref name="draft">
2024
- <a:documentation>The identifier for the current draft of the bibliographic item</a:documentation>
2025
- </ref>
2018
+ <attribute name="type">
2019
+ <a:documentation>Versioning scheme, in case of multiple versioning schemes</a:documentation>
2020
+ </attribute>
2026
2021
  </optional>
2022
+ <text/>
2027
2023
  </element>
2028
2024
  </define>
2029
2025
  <define name="vedition">
@@ -1,6 +1,7 @@
1
1
  module Relaton
2
2
  module Iho
3
3
  class Bibdata < Item
4
+ model ItemData
4
5
  include Bib::BibdataShared
5
6
  end
6
7
  end
@@ -1,6 +1,7 @@
1
1
  module Relaton
2
2
  module Iho
3
3
  class Bibitem < Item
4
+ model ItemData
4
5
  include Bib::BibitemShared
5
6
  end
6
7
  end
@@ -2,7 +2,7 @@ require "net/http"
2
2
 
3
3
  module Relaton
4
4
  module Iho
5
- module Bibliography
5
+ module Bibliography
6
6
  ENDPOINT = "https://raw.githubusercontent.com/relaton/relaton-data-iho/refs/heads/data-v2/".freeze
7
7
 
8
8
  class << self
@@ -14,12 +14,11 @@ module Relaton
14
14
  # @return [RelatonIho::IhoBibliographicItem, nil] the IHO standard or nil if not found
15
15
  #
16
16
  def search(text, _year = nil, _opts = {}) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
17
- Util.info "Fetching from Relaton repository ...", key: text
18
- ref = text.sub(/^IHO\s/, "").sub(/^([[:alpha:]]+)(\d+)/, '\1-\2')
19
- index = Relaton::Index.find_or_create :iho, url: "#{ENDPOINT}#{INDEXFILE}.zip"
20
- row = index.search(ref).min_by { |r| r[:id] }
17
+ pubid = text.is_a?(String) ? ::Pubid::Iho::Identifier.parse(text) : text
18
+ Util.info "Fetching from Relaton repository ...", key: pubid.to_s
19
+ row = index.search { |r| pubid_match?(r[:id], pubid) }.min_by { |r| row_version(r[:id]) }
21
20
  unless row
22
- Util.info "Not found.", key: text
21
+ Util.info "Not found.", key: pubid.to_s
23
22
  return
24
23
  end
25
24
 
@@ -30,7 +29,8 @@ module Relaton
30
29
  end
31
30
 
32
31
  item = Relaton::Iho::Item.from_yaml resp.body
33
- Util.info "Found: `#{item.docidentifier.first.content}`", key: text
32
+ enrich_with_pubid(item, pubid)
33
+ Util.info "Found: `#{item.docidentifier.first.content}`", key: pubid.to_s
34
34
  item.tap { |i| i.fetched = Date.today.to_s }
35
35
  rescue SocketError, Errno::EINVAL, Errno::ECONNRESET, EOFError,
36
36
  Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
@@ -39,6 +39,31 @@ module Relaton
39
39
  raise Relaton::RequestError, "Could not access #{uri}: #{e.message}"
40
40
  end
41
41
 
42
+ # Populate ext.structuredidentifier from the parsed Pubid when the
43
+ # fetched record doesn't already provide one. Maps `pubid.number`
44
+ # (with type prefix, e.g. `S-100`) -> docnumber, `pubid.part` -> part,
45
+ # `pubid.appendix` -> appendixid, `pubid.annex` -> annexid,
46
+ # `pubid.supplement` -> supplementid.
47
+ def enrich_with_pubid(item, pubid)
48
+ return if item.ext&.structuredidentifier&.any?
49
+
50
+ sid = StructuredIdentifier.new(
51
+ docnumber: pubid_docnumber(pubid),
52
+ part: pubid.part,
53
+ appendixid: (pubid.appendix if pubid.respond_to?(:appendix)),
54
+ annexid: (pubid.annex if pubid.respond_to?(:annex)),
55
+ supplementid: (pubid.supplement if pubid.respond_to?(:supplement)),
56
+ )
57
+ item.ext ||= Ext.new
58
+ item.ext.structuredidentifier = [sid]
59
+ end
60
+
61
+ # "S-100" rather than "100" — keeps the type prefix that distinguishes
62
+ # the IHO series (S/P/M/B/C). Matches spec/fixtures/iho_part.xml.
63
+ def pubid_docnumber(pubid)
64
+ pubid.to_s.sub(/^IHO\s/, "").split(/\s+/, 2).first
65
+ end
66
+
42
67
  # @param ref [String] the IHO standard Code to look up (e..g "IHO B-11")
43
68
  # @param year [String] the year the standard was published (optional)
44
69
  #
@@ -51,6 +76,49 @@ module Relaton
51
76
  def get(ref, year = nil, opts = {})
52
77
  search(ref, year, opts)
53
78
  end
79
+
80
+ private
81
+
82
+ def index
83
+ Relaton::Index.find_or_create(
84
+ :iho,
85
+ url: "#{ENDPOINT}#{INDEXFILE}.zip",
86
+ file: "#{INDEXFILE}.yaml",
87
+ id_keys: %i[publisher number type version part appendix annex supplement],
88
+ pubid_class: ::Pubid::Iho::Identifier,
89
+ )
90
+ end
91
+
92
+ def pubid_match?(row_id, query)
93
+ row_attrs = row_attributes(row_id)
94
+ return false unless row_attrs
95
+
96
+ query_attrs = query.to_h
97
+ # Subdivision keys (part/appendix/annex/supplement) use strict
98
+ # equality — a nil query must match a nil row (the umbrella),
99
+ # not an arbitrary subdivision under the same (number, version).
100
+ # Only :version stays nil-tolerant: an unqualified `IHO B-11`
101
+ # query is expected to find the latest edition.
102
+ row_attrs[:publisher] == query_attrs[:publisher] &&
103
+ row_attrs[:type] == query_attrs[:type] &&
104
+ row_attrs[:number] == query_attrs[:number] &&
105
+ (query_attrs[:version].nil? || row_attrs[:version].to_s == query_attrs[:version].to_s) &&
106
+ row_attrs[:part].to_s == query_attrs[:part].to_s &&
107
+ row_attrs[:appendix].to_s == query_attrs[:appendix].to_s &&
108
+ row_attrs[:annex].to_s == query_attrs[:annex].to_s &&
109
+ row_attrs[:supplement].to_s == query_attrs[:supplement].to_s
110
+ end
111
+
112
+ def row_attributes(row_id)
113
+ return row_id.to_h if row_id.is_a?(::Pubid::Core::Identifier::Base)
114
+ return row_id if row_id.is_a?(Hash)
115
+
116
+ nil
117
+ end
118
+
119
+ def row_version(row_id)
120
+ row_attributes(row_id)&.dig(:version).to_s
121
+ end
54
122
  end
55
123
  end
56
124
  end
@@ -0,0 +1,35 @@
1
+ module Relaton
2
+ module Iho
3
+ class Docidentifier < Bib::Docidentifier
4
+ attr_reader :pubid
5
+
6
+ def initialize(attrs = {}, options = {})
7
+ pubid = attrs.is_a?(Hash) ? attrs.delete(:pubid) : nil
8
+ attrs[:content] ||= pubid.to_s if pubid
9
+ super
10
+ @pubid = pubid if pubid
11
+ end
12
+
13
+ def content=(value)
14
+ super
15
+ @pubid = ::Pubid::Iho::Identifier.parse(value) if value
16
+ end
17
+
18
+ def to_h
19
+ @pubid&.to_h || super
20
+ end
21
+
22
+ def remove_part!
23
+ @pubid&.part = nil
24
+ end
25
+
26
+ def remove_date!
27
+ @pubid&.year = nil
28
+ end
29
+
30
+ def to_all_parts!
31
+ @pubid&.all_parts = true
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,15 +1,18 @@
1
1
  require_relative "doctype"
2
2
  require_relative "comment_period"
3
+ require_relative "structured_identifier"
3
4
 
4
5
  module Relaton
5
6
  module Iho
6
7
  class Ext < Bib::Ext
7
8
  attribute :doctype, Doctype, default: -> { Doctype.new(content: "standard") }
8
9
  attribute :commentperiod, CommentPeriod
10
+ attribute :structuredidentifier, StructuredIdentifier,
11
+ collection: true, initialize_empty: true
9
12
 
10
- xml do
11
- map_element "commentperiod", to: :commentperiod
12
- end
13
+ xml { map_element "commentperiod", to: :commentperiod }
14
+
15
+ key_value { map_element "commentperiod", to: :commentperiod }
13
16
 
14
17
  def schema_version
15
18
  Relaton.schema_versions["relaton-model-iho"]
@@ -19,9 +19,20 @@ module Relaton
19
19
  ret[:ext][:flavor] ||= flavor(ret)
20
20
  ics_hash_to_bib ret
21
21
  commentperiod_hash_to_bib ret
22
+ structuredidentifier_hash_to_bib ret
22
23
  ret[:ext] = Ext.new(**ret[:ext])
23
24
  end
24
25
 
26
+ def structuredidentifier_hash_to_bib(ret)
27
+ sid = ret.dig(:ext, :structuredidentifier) || ret[:structuredidentifier]
28
+ return unless sid
29
+
30
+ ret[:ext]&.delete(:structuredidentifier)
31
+ ret.delete(:structuredidentifier)
32
+ ret[:ext][:structuredidentifier] =
33
+ array(sid).map { |s| StructuredIdentifier.new(**s) }
34
+ end
35
+
25
36
  def commentperiod_hash_to_bib(ret)
26
37
  cp = ret.dig(:ext, :commentperiod) || ret[:commentperiod]
27
38
  return unless cp
@@ -124,6 +135,10 @@ module Relaton
124
135
  def create_relation(rel)
125
136
  Relation.new(**rel)
126
137
  end
138
+
139
+ def create_docid(**args)
140
+ Docidentifier.new(**args)
141
+ end
127
142
  end
128
143
  end
129
144
  end
@@ -1,5 +1,6 @@
1
1
  require_relative "item_data"
2
2
  require_relative "ext"
3
+ require_relative "docidentifier"
3
4
 
4
5
  module Relaton
5
6
  module Iho
@@ -7,6 +8,8 @@ module Relaton
7
8
  model ItemData
8
9
 
9
10
  attribute :ext, Ext
11
+ attribute :docidentifier, Docidentifier, collection: true,
12
+ initialize_empty: true
10
13
  end
11
14
  end
12
15
  end
@@ -1,8 +1,18 @@
1
1
  module Relaton
2
2
  module Iho
3
- class ItemBase < Item
3
+ class ItemBase < Lutaml::Model::Serializable
4
+ include Bib::NamespaceHelper
5
+
6
+ attr_accessor :type
7
+
4
8
  model ItemData
5
- include Bib::ItemBaseAttributes
9
+
10
+ instance_exec(&Bib::ItemShared::ATTRIBUTES)
11
+
12
+ xml do
13
+ map_attribute "type", to: :type
14
+ instance_exec(&Bib::ItemShared::XML_BODY)
15
+ end
6
16
  end
7
17
  end
8
18
  end
@@ -0,0 +1,20 @@
1
+ module Relaton
2
+ module Iho
3
+ class StructuredIdentifier < Lutaml::Model::Serializable
4
+ attribute :docnumber, :string
5
+ attribute :part, :string
6
+ attribute :annexid, :string
7
+ attribute :appendixid, :string
8
+ attribute :supplementid, :string
9
+
10
+ xml do
11
+ root "structuredidentifier"
12
+ map_element "docnumber", to: :docnumber
13
+ map_element "part", to: :part
14
+ map_element "annexid", to: :annexid
15
+ map_element "appendixid", to: :appendixid
16
+ map_element "supplementid", to: :supplementid
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,5 @@
1
1
  module Relaton
2
2
  module Iho
3
- VERSION = "2.0.0".freeze
3
+ VERSION = "2.1.0".freeze
4
4
  end
5
5
  end
data/lib/relaton/iho.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require "relaton/bib"
2
2
  require "relaton/index"
3
+ require "pubid/iho"
3
4
  require_relative "iho/version"
4
5
  require_relative "iho/util"
6
+ require_relative "iho/docidentifier"
5
7
  require_relative "iho/item"
6
8
  require_relative "iho/bibitem"
7
9
  require_relative "iho/bibdata"
@@ -9,7 +11,7 @@ require_relative "iho/bibliography"
9
11
 
10
12
  module Relaton
11
13
  module Iho
12
- INDEXFILE = "index-v1".freeze
14
+ INDEXFILE = "index-v2".freeze
13
15
 
14
16
  class Error < StandardError; end
15
17
 
@@ -22,7 +22,8 @@ Gem::Specification.new do |s|
22
22
  s.required_ruby_version = Gem::Requirement.new(">= 3.2.0")
23
23
 
24
24
  s.add_dependency "base64"
25
- s.add_dependency "relaton-bib", "~> 2.0.0"
25
+ s.add_dependency "pubid-iho", "~> 1.15.16"
26
+ s.add_dependency "relaton-bib", "~> 2.1.0"
26
27
  s.add_dependency "relaton-core", "~> 0.0.13"
27
28
  s.add_dependency "relaton-index", "~> 0.2.0"
28
29
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: relaton-iho
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-05-04 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: base64
@@ -23,20 +24,34 @@ dependencies:
23
24
  - - ">="
24
25
  - !ruby/object:Gem::Version
25
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pubid-iho
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.15.16
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.15.16
26
41
  - !ruby/object:Gem::Dependency
27
42
  name: relaton-bib
28
43
  requirement: !ruby/object:Gem::Requirement
29
44
  requirements:
30
45
  - - "~>"
31
46
  - !ruby/object:Gem::Version
32
- version: 2.0.0
47
+ version: 2.1.0
33
48
  type: :runtime
34
49
  prerelease: false
35
50
  version_requirements: !ruby/object:Gem::Requirement
36
51
  requirements:
37
52
  - - "~>"
38
53
  - !ruby/object:Gem::Version
39
- version: 2.0.0
54
+ version: 2.1.0
40
55
  - !ruby/object:Gem::Dependency
41
56
  name: relaton-core
42
57
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +108,7 @@ files:
93
108
  - lib/relaton/iho/bibitem.rb
94
109
  - lib/relaton/iho/bibliography.rb
95
110
  - lib/relaton/iho/comment_period.rb
111
+ - lib/relaton/iho/docidentifier.rb
96
112
  - lib/relaton/iho/doctype.rb
97
113
  - lib/relaton/iho/ext.rb
98
114
  - lib/relaton/iho/hash_parser_v1.rb
@@ -101,13 +117,15 @@ files:
101
117
  - lib/relaton/iho/item_data.rb
102
118
  - lib/relaton/iho/processor.rb
103
119
  - lib/relaton/iho/relation.rb
120
+ - lib/relaton/iho/structured_identifier.rb
104
121
  - lib/relaton/iho/util.rb
105
122
  - lib/relaton/iho/version.rb
106
- - relaton_iho.gemspec
123
+ - relaton-iho.gemspec
107
124
  homepage: https://github.com/relaton/relaton-iho
108
125
  licenses:
109
126
  - BSD-2-Clause
110
127
  metadata: {}
128
+ post_install_message:
111
129
  rdoc_options: []
112
130
  require_paths:
113
131
  - lib
@@ -122,7 +140,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
140
  - !ruby/object:Gem::Version
123
141
  version: '0'
124
142
  requirements: []
125
- rubygems_version: 3.6.9
143
+ rubygems_version: 3.5.22
144
+ signing_key:
126
145
  specification_version: 4
127
146
  summary: 'Relaton::Iho: retrieve IHO Standards for bibliographic using the BibliographicItem
128
147
  model'