pubid 2.0.2 → 2.0.4

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/data/ieee/update_codes.yaml +2 -2
  3. data/data/parg/aiee.json +1 -1
  4. data/data/parg/aiee.parg +99 -9
  5. data/data/parg/iala.json +1 -1
  6. data/data/parg/ieee.json +1 -1
  7. data/data/parg/ire.json +1 -1
  8. data/data/parg/ire.parg +51 -12
  9. data/data/parg/iso.json +1 -1
  10. data/data/parg/iso.parg +751 -0
  11. data/data/parg/jis.json +1 -1
  12. data/data/parg/nesc.json +1 -1
  13. data/data/parg/nesc.parg +97 -12
  14. data/data/parg/parg.json +1 -1
  15. data/lib/pubid/iala/builder.rb +48 -39
  16. data/lib/pubid/iala/identifier.rb +1 -1
  17. data/lib/pubid/ieee/CLAUDE.md +2 -0
  18. data/lib/pubid/ieee/aiee/identifier.rb +1 -1
  19. data/lib/pubid/ieee/builder.rb +175 -28
  20. data/lib/pubid/ieee/components/code.rb +46 -7
  21. data/lib/pubid/ieee/identifiers/base.rb +4 -1
  22. data/lib/pubid/ieee/identifiers/joint_development.rb +7 -3
  23. data/lib/pubid/ieee/ire/identifier.rb +1 -1
  24. data/lib/pubid/ieee/nesc/builder.rb +1 -1
  25. data/lib/pubid/ieee/project_renderer.rb +5 -1
  26. data/lib/pubid/ieee/renderer.rb +4 -2
  27. data/lib/pubid/ieee/urn_generator.rb +4 -1
  28. data/lib/pubid/iho/CLAUDE.md +15 -6
  29. data/lib/pubid/iho/urn_generator.rb +9 -6
  30. data/lib/pubid/iho/urn_parser.rb +43 -40
  31. data/lib/pubid/iso/CLAUDE.md +66 -0
  32. data/lib/pubid/iso/builder.rb +228 -14
  33. data/lib/pubid/iso/identifier.rb +22 -12
  34. data/lib/pubid/iso/normalizer.rb +5 -4
  35. data/lib/pubid/iso/urn_parser.rb +13 -8
  36. data/lib/pubid/jis/CLAUDE.md +36 -0
  37. data/lib/pubid/jis/builder.rb +12 -12
  38. data/lib/pubid/jis/identifier.rb +11 -1
  39. data/lib/pubid/jis/identifiers/welding_engineering_standard.rb +35 -0
  40. data/lib/pubid/jis/identifiers.rb +2 -0
  41. data/lib/pubid/jis/renderer.rb +1 -1
  42. data/lib/pubid/jis/supplement_identifier.rb +5 -5
  43. data/lib/pubid/jis/urn_parser.rb +2 -1
  44. data/lib/pubid/jis.rb +3 -2
  45. data/lib/pubid/version.rb +1 -1
  46. metadata +9 -12
@@ -135,8 +135,10 @@ module Pubid
135
135
  map "languages", to: :languages
136
136
  # publisher emitted only when the primary isn't the type default;
137
137
  # copublishers (the other bodies) as an array, omitted when empty.
138
- map "publisher", with: { to: :publisher_to_kv, from: :publisher_from_kv }
139
- map "copublishers", with: { to: :copublishers_to_kv, from: :copublishers_from_kv }
138
+ map "publisher",
139
+ with: { to: :publisher_to_kv, from: :publisher_from_kv }
140
+ map "copublishers",
141
+ with: { to: :copublishers_to_kv, from: :copublishers_from_kv }
140
142
  map "locality", to: :locality
141
143
  # `type` and generic `stage` are fully derived from `typed_stage`
142
144
  # (builder sets them via to_type/to_stage), so we serialize only the
@@ -169,8 +171,10 @@ module Pubid
169
171
  # instead of falling back. The IEC twin (iec/identifier.rb) was
170
172
  # migrated at the time; ISO was missed.
171
173
  ts = (model.class.const_defined?(:TYPED_STAGES) &&
172
- model.class::TYPED_STAGES.find { |t| t.code.to_s == value.to_s }) ||
173
- Pubid::Iso.locate_stage_by_code(value)
174
+ model.class::TYPED_STAGES.find do |t|
175
+ t.code.to_s == value.to_s
176
+ end) ||
177
+ Pubid::Iso.locate_stage_by_code(value)
174
178
  return unless ts
175
179
 
176
180
  # The renderer prefers `original_abbr` (the parsed surface form); without
@@ -196,7 +200,7 @@ module Pubid
196
200
  return if v.nil? || v.to_s.empty?
197
201
 
198
202
  doc.add_child(Lutaml::KeyValue::DataModel::Element.new("stage_iteration",
199
- v.to_s))
203
+ v.to_s))
200
204
  end
201
205
 
202
206
  def stage_iteration_from_kv(model, value)
@@ -219,7 +223,11 @@ module Pubid
219
223
  # --- date serialized flat as year/month/day ---
220
224
  def year_to_kv(model, doc) = emit_date_part(doc, "year", model.date&.year)
221
225
  def year_from_kv(model, value) = date_for(model).year = value.to_s
222
- def month_to_kv(model, doc) = emit_date_part(doc, "month", model.date&.month)
226
+
227
+ def month_to_kv(model, doc)
228
+ emit_date_part(doc, "month", model.date&.month)
229
+ end
230
+
223
231
  def month_from_kv(model, value) = date_for(model).month = value.to_s
224
232
  def day_to_kv(model, doc) = emit_date_part(doc, "day", model.date&.day)
225
233
  def day_from_kv(model, value) = date_for(model).day = value.to_s
@@ -252,7 +260,8 @@ module Pubid
252
260
  pub = model.publisher&.publisher
253
261
  return if pub.nil? || pub == model.class.default_publisher&.publisher
254
262
 
255
- doc.add_child(Lutaml::KeyValue::DataModel::Element.new("publisher", pub))
263
+ doc.add_child(Lutaml::KeyValue::DataModel::Element.new("publisher",
264
+ pub))
256
265
  end
257
266
 
258
267
  def publisher_from_kv(model, value)
@@ -264,7 +273,8 @@ module Pubid
264
273
  return unless cp&.any?
265
274
 
266
275
  doc.add_child(
267
- Lutaml::KeyValue::DataModel::Element.new("copublishers", cp.map(&:to_s)),
276
+ Lutaml::KeyValue::DataModel::Element.new("copublishers",
277
+ cp.map(&:to_s)),
268
278
  )
269
279
  end
270
280
 
@@ -304,10 +314,10 @@ module Pubid
304
314
  when :mr_string
305
315
  Pubid::Parsers::MrString.parse(string)
306
316
  else
307
- # R1 parser swap: the baked PG artifact is the identifier
308
- # parser of record; the Builder consumes the same attribute
309
- # hash it always has.
310
- parsed = Pubid::Parg::Backend.parse(:iso, string)
317
+ # The baked PG artifact parses through the grammar's declared
318
+ # bindings: the Builder consumes the typed bound map (every
319
+ # capture bound by name, supplement layers position-scoped).
320
+ parsed = Pubid::Parg::Model.bound(:iso, string)
311
321
  Pubid::Iso::Builder.new.build(parsed)
312
322
  end
313
323
  end
@@ -7,7 +7,7 @@ module Pubid
7
7
  # update_codes regex rewrites, and the DAD/FDAM pattern that the PEG
8
8
  # parser cannot recognize because it treats "/" as a copublisher separator.
9
9
  class Normalizer
10
- DAD_PATTERN = /^(.+?)\/(F?DAD)\s+(\d+)(?::(\d{4}))?$/.freeze
10
+ DAD_PATTERN = /^(.+?)\/(F?DAD)\s+(\d+)(?::(\d{4}))?$/
11
11
 
12
12
  class << self
13
13
  # Apply all normalizations to an identifier string and return the
@@ -29,8 +29,9 @@ module Pubid
29
29
  def parse_with_builder(string)
30
30
  # R2 ingestion hook: normalized strings reach the model through
31
31
  # the same parser of record as Identifier.parse (the baked PG
32
- # artifact), so every Tier-3 normalization feeds one grammar.
33
- parsed = Pubid::Parg::Backend.parse(:iso, string)
32
+ # artifact's bindings), so every Tier-3 normalization feeds one
33
+ # grammar.
34
+ parsed = Pubid::Parg::Model.bound(:iso, string)
34
35
  Pubid::Iso.builder.build(parsed)
35
36
  end
36
37
 
@@ -71,7 +72,7 @@ module Pubid
71
72
  supplement_number = match[3] # "1"
72
73
  supplement_year = match[4] # "1987" or nil
73
74
 
74
- base_parsed = Pubid::Parg::Backend.parse(:iso, base_str)
75
+ base_parsed = Pubid::Parg::Model.bound(:iso, base_str)
75
76
  base = Pubid::Iso.builder.build(base_parsed)
76
77
 
77
78
  addendum = Identifiers::Addendum.new
@@ -437,9 +437,14 @@ module Pubid
437
437
  base_hash[:languages] = languages.join("/")
438
438
  end
439
439
 
440
- # Build supplements recursively (forward order: first supplement wraps base,
441
- # second wraps first, etc. -- matching URN left-to-right semantics)
442
- supplements.each do |supp|
440
+ # Build supplements (forward order: first supplement wraps base,
441
+ # second wraps first, etc. -- matching URN left-to-right
442
+ # semantics). Each layer captures into the bound hash's
443
+ # position-scoped prefix (supp_ / supp2_ / supp3_), the same
444
+ # names the grammar binds, so the builder peels the wrappers the
445
+ # same way for both parse paths.
446
+ supplements.each_with_index do |supp, index|
447
+ prefix = index.zero? ? "supp_" : "supp#{index + 1}_"
443
448
  # A bare language token with no supplement identity names the BASE
444
449
  # document: wrapping it would build a supplement with no type,
445
450
  # number or base attribute, and a plain standard drops its base —
@@ -499,11 +504,11 @@ typed_stage
499
504
  supp_hash[:languages] = supp[:languages].join("/")
500
505
  end
501
506
 
502
- # Wrap current identifier with supplement
503
- base_hash = {
504
- base: base_hash,
505
- **supp_hash,
506
- }
507
+ # Layer the supplement onto the flat bound hash under its
508
+ # position-scoped prefix
509
+ supp_hash.each do |key, value|
510
+ base_hash[:"#{prefix}#{key}"] = value
511
+ end
507
512
  end
508
513
 
509
514
  # all_parts belongs on the outermost identifier (after any supplement wrapping)
@@ -0,0 +1,36 @@
1
+ # JIS flavor notes
2
+
3
+ ## WES lives in the JIS flavor (relaton-jis#18)
4
+
5
+ - WES (Japan Welding Engineering Society) ids — `WES 7700-1:2019`,
6
+ `WES 7301:2020R` — parse as `Identifiers::WeldingEngineeringStandard`
7
+ (`_type: pubid:jis:welding-engineering-standard`). JSA Webdesk sells WES next
8
+ to JIS and relaton crawls both into one `relaton-data-jis` index, so a
9
+ separate `Pubid::Wes` flavor was rejected.
10
+ - **Grammar**: `data/parg/jis.json` is `identifier = wes_identifier /
11
+ jis_identifier`. The WES branch is `"WES" space number parts …` with the same
12
+ tail as JIS (year, `R`, language, 規格群, supplements, SYMBOL) and binds a
13
+ `wes` capture, which the builder keys on. `JIS WES 7700` and `WES X 7700`
14
+ are rejected: the JIS prefix and a series letter belong only to JIS. The
15
+ source of record is pubid/pubid-grammar; the artifact was rebaked here with
16
+ parsanol **1.3.114** — the PATH `parsanol` shim may be a newer version, so
17
+ bake with the 1.3.114 gem's `exe/parsanol` and check `compiler_version`.
18
+ - **`series` is nil** for WES (JIS keeps its `""`-coerced String), so the hash
19
+ carries no `series` key. `number` is the bare number (`"7700"`), the index
20
+ key.
21
+ - **"WES" is the root, not a publisher**: `publisher_prefix` is nil, `code`
22
+ is `"WES 7700-1"`, so `to_s(with_publisher: false)` still prints `WES …`,
23
+ also as the base of a supplement. The URN is `urn:jis:WES 7700-1:2019`;
24
+ `UrnParser` does not prepend `JIS ` to a code that starts with `WES `.
25
+ - `PREFIXES` includes `"WES"`, so `Pubid.parse("WES …")` routes to JIS.
26
+
27
+ ## Equality includes the class
28
+
29
+ - `Identifier#==` requires `other.instance_of?(self.class)`: `JIS TR X
30
+ 0014:1999` is not `JIS X 0014:1999` (3 such pairs are in the published
31
+ index), and `WES 7700` is never a JIS standard. `#matches?` follows, since it
32
+ compares `exclude(…)` results with `==`.
33
+ - `SupplementIdentifier#==` also compares `symbol` and `reaffirmed?`:
34
+ `…/AMD 3:2025R SYMBOL 65700` is not `…/AMD 3:2025`.
35
+ - The all-parts match is `===` (`Pubid::AllParts`), not `==`, and is
36
+ unchanged.
@@ -37,26 +37,26 @@ module Pubid
37
37
 
38
38
  def build_single_identifier(data)
39
39
  attrs = {
40
- series: data[:series].to_s,
41
40
  number: data[:number].to_s,
42
41
  parts: extract_part_strings(data[:parts]),
43
42
  year: data[:year]&.to_i,
44
43
  language: data[:language]&.to_s,
45
44
  reaffirmed: (true if data[:reaffirmed]),
46
45
  }
46
+ # A WES id carries no series letter, so its series stays nil.
47
+ wes = Identifiers::WeldingEngineeringStandard
48
+ return wes.new(**attrs) if data[:wes]
47
49
 
48
- # Determine identifier class from type
49
- type = data[:type]&.to_s
50
- klass = case type
51
- when "TR"
52
- Identifiers::TechnicalReport
53
- when "TS"
54
- Identifiers::TechnicalSpecification
55
- else
56
- Identifiers::JapaneseIndustrialStandard
57
- end
50
+ jis_class_for_type(data[:type]&.to_s)
51
+ .new(series: data[:series].to_s, **attrs)
52
+ end
58
53
 
59
- klass.new(**attrs)
54
+ def jis_class_for_type(type)
55
+ case type
56
+ when "TR" then Identifiers::TechnicalReport
57
+ when "TS" then Identifiers::TechnicalSpecification
58
+ else Identifiers::JapaneseIndustrialStandard
59
+ end
60
60
  end
61
61
 
62
62
  def build_amendment(data)
@@ -43,6 +43,8 @@ module Pubid
43
43
  "pubid:jis:amendment" => "Pubid::Jis::Identifiers::Amendment",
44
44
  "pubid:jis:corrigendum" => "Pubid::Jis::Identifiers::Corrigendum",
45
45
  "pubid:jis:explanation" => "Pubid::Jis::Identifiers::Explanation",
46
+ "pubid:jis:welding-engineering-standard" =>
47
+ "Pubid::Jis::Identifiers::WeldingEngineeringStandard",
46
48
  }.freeze
47
49
 
48
50
  key_value do
@@ -63,6 +65,12 @@ module Pubid
63
65
  # would otherwise fail serialization type validation.
64
66
  PUBLISHER = "JIS"
65
67
 
68
+ # The token the human render prints before the designation, and drops
69
+ # for `to_s(with_publisher: false)`; nil when the designation has none.
70
+ def publisher_prefix
71
+ PUBLISHER
72
+ end
73
+
66
74
  def reaffirmed?
67
75
  reaffirmed == true
68
76
  end
@@ -88,8 +96,10 @@ module Pubid
88
96
  result
89
97
  end
90
98
 
99
+ # The class is part of the identity: "JIS TR X 0014:1999" is not
100
+ # "JIS X 0014:1999", and "WES 7700" is not a JIS standard.
91
101
  def ==(other)
92
- return false unless other.is_a?(Identifier)
102
+ return false unless other.instance_of?(self.class)
93
103
 
94
104
  series == other.series &&
95
105
  number == other.number &&
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Jis
5
+ module Identifiers
6
+ # Japan Welding Engineering Society standard. JSA sells WES next to JIS,
7
+ # so it parses through the JIS flavor, but the id carries no series
8
+ # letter and no JIS prefix.
9
+ # Examples: WES 7700-1:2019, WES 7301:2020R
10
+ class WeldingEngineeringStandard < SingleIdentifier
11
+ PUBLISHER = "WES"
12
+
13
+ def type_prefix
14
+ nil
15
+ end
16
+
17
+ # "WES" is the root of the designation, not a publisher token, so
18
+ # `to_s(with_publisher: false)` keeps it.
19
+ def publisher_prefix
20
+ nil
21
+ end
22
+
23
+ def code
24
+ result = "#{PUBLISHER} #{number}"
25
+ result += parts.map { |p| "-#{p}" }.join if parts&.any?
26
+ result
27
+ end
28
+
29
+ def mr_publisher
30
+ PUBLISHER.downcase
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -13,6 +13,8 @@ module Pubid
13
13
  autoload :TechnicalReport, "#{__dir__}/identifiers/technical_report"
14
14
  autoload :TechnicalSpecification,
15
15
  "#{__dir__}/identifiers/technical_specification"
16
+ autoload :WeldingEngineeringStandard,
17
+ "#{__dir__}/identifiers/welding_engineering_standard"
16
18
  end
17
19
  end
18
20
  end
@@ -44,7 +44,7 @@ module Pubid
44
44
  def render_single(id)
45
45
  with_pub = with_publisher?(id)
46
46
  parts = []
47
- parts << PUBLISHER if with_pub
47
+ parts << id.publisher_prefix if with_pub && id.publisher_prefix
48
48
  parts << id.type_prefix if id.class.method_defined?(:type_prefix) && id.type_prefix
49
49
  result = parts.join(" ")
50
50
  result += " " if result.length.positive?
@@ -38,12 +38,12 @@ module Pubid
38
38
  end
39
39
 
40
40
  def ==(other)
41
- return false unless other.is_a?(SupplementIdentifier)
42
- return false unless other.class == self.class
41
+ return false unless other.instance_of?(self.class)
43
42
 
44
- base == other.base &&
45
- number == other.number &&
46
- year == other.year
43
+ # A supplement carries its own reaffirmation and SYMBOL clause.
44
+ %i[base number year reaffirmed? symbol].all? do |field|
45
+ public_send(field) == other.public_send(field)
46
+ end
47
47
  end
48
48
  end
49
49
  end
@@ -9,12 +9,13 @@ module Pubid
9
9
  #
10
10
  # Examples:
11
11
  # - urn:jis:X 0201:1997 → JIS X 0201:1997
12
+ # - urn:jis:WES 7700-1:2019 → WES 7700-1:2019 (the code carries its root)
12
13
  class UrnParser < Pubid::UrnParser::Base
13
14
  def parse_urn(urn)
14
15
  body = strip_namespace(urn)
15
16
  parts = split_parts(body)
16
17
  code, year = parts
17
- text = "JIS #{code}"
18
+ text = code.to_s.start_with?("WES ") ? code.dup : "JIS #{code}"
18
19
  text += ":#{year}" if year
19
20
  flavor_parse(text)
20
21
  end
data/lib/pubid/jis.rb CHANGED
@@ -5,9 +5,10 @@ module Pubid
5
5
  module Jis
6
6
  extend Pubid::PrefixesSupport
7
7
 
8
- # Sole JIS publisher token (see the parser's `jis_prefix` rule). Document
8
+ # The JIS publisher token, and WES, the root of a Japan Welding
9
+ # Engineering Society id (the grammar's `wes_identifier` rule). Document
9
10
  # types (TR/TS) follow the JIS prefix and are excluded.
10
- PREFIXES = ["JIS"].freeze
11
+ PREFIXES = ["JIS", "WES"].freeze
11
12
 
12
13
  autoload :Builder, "#{__dir__}/jis/builder"
13
14
  autoload :Identifier, "#{__dir__}/jis/identifier"
data/lib/pubid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pubid
4
- VERSION = "2.0.2"
4
+ VERSION = "2.0.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pubid
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-11-01 00:00:00.000000000 Z
11
+ date: 2026-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lutaml-model
@@ -28,22 +28,16 @@ dependencies:
28
28
  name: parsanol
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 1.3.114
34
- - - "<"
31
+ - - "~>"
35
32
  - !ruby/object:Gem::Version
36
- version: '1.4'
33
+ version: 1.3.122
37
34
  type: :runtime
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- version: 1.3.114
44
- - - "<"
38
+ - - "~>"
45
39
  - !ruby/object:Gem::Version
46
- version: '1.4'
40
+ version: 1.3.122
47
41
  description: PubID is a Ruby library for parsing and generating international publication
48
42
  identifiers (PubIDs). It provides a simple and flexible API for working with PubIDs,
49
43
  allowing developers to easily create, manipulate, and validate PubIDs in their applications.
@@ -103,6 +97,7 @@ files:
103
97
  - data/parg/ire.parg
104
98
  - data/parg/isbn.json
105
99
  - data/parg/iso.json
100
+ - data/parg/iso.parg
106
101
  - data/parg/itu.json
107
102
  - data/parg/jcgm.json
108
103
  - data/parg/jis.json
@@ -792,6 +787,7 @@ files:
792
787
  - lib/pubid/jcgm/urn_generator.rb
793
788
  - lib/pubid/jcgm/urn_parser.rb
794
789
  - lib/pubid/jis.rb
790
+ - lib/pubid/jis/CLAUDE.md
795
791
  - lib/pubid/jis/builder.rb
796
792
  - lib/pubid/jis/identifier.rb
797
793
  - lib/pubid/jis/identifiers.rb
@@ -803,6 +799,7 @@ files:
803
799
  - lib/pubid/jis/identifiers/standard.rb
804
800
  - lib/pubid/jis/identifiers/technical_report.rb
805
801
  - lib/pubid/jis/identifiers/technical_specification.rb
802
+ - lib/pubid/jis/identifiers/welding_engineering_standard.rb
806
803
  - lib/pubid/jis/parser.rb
807
804
  - lib/pubid/jis/renderer.rb
808
805
  - lib/pubid/jis/single_identifier.rb