pubid 2.0.20 → 2.0.21
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/data/iho/s4-reserved-specs.yaml +15 -0
- data/data/parg/bipm.json +1 -1
- data/data/parg/bipm.parg +46 -11
- data/data/parg/iho.json +1 -1
- data/data/parg/iho.parg +128 -11
- data/data/parg/plateau.json +1 -1
- data/lib/pubid/bipm/CLAUDE.md +6 -1
- data/lib/pubid/bipm/builder.rb +20 -1
- data/lib/pubid/bipm/renderer.rb +14 -2
- data/lib/pubid/bipm/urn_generator.rb +80 -14
- data/lib/pubid/bipm/urn_parser.rb +70 -6
- data/lib/pubid/ecma/CLAUDE.md +12 -0
- data/lib/pubid/ecma/identifier.rb +1 -2
- data/lib/pubid/ecma/parser.rb +49 -0
- data/lib/pubid/iec/CLAUDE.md +5 -1
- data/lib/pubid/iec/identifier.rb +19 -70
- data/lib/pubid/iec/identifiers/fragment_identifier.rb +3 -3
- data/lib/pubid/iec/single_identifier.rb +69 -4
- data/lib/pubid/iec/supplement_identifier.rb +52 -76
- data/lib/pubid/iho/CLAUDE.md +52 -0
- data/lib/pubid/iho/builder.rb +105 -2
- data/lib/pubid/iho/identifiers/base.rb +52 -1
- data/lib/pubid/iho/pubid_urn_schema.rb +11 -4
- data/lib/pubid/iho/renderer.rb +39 -2
- data/lib/pubid/iho/reserved_specs.rb +54 -0
- data/lib/pubid/iho/urn_generator.rb +6 -0
- data/lib/pubid/iho/urn_parser.rb +50 -9
- data/lib/pubid/iho.rb +1 -0
- data/lib/pubid/nist/CLAUDE.md +11 -0
- data/lib/pubid/nist/preprocessor.rb +16 -0
- data/lib/pubid/plateau/CLAUDE.md +35 -0
- data/lib/pubid/plateau/builder.rb +4 -0
- data/lib/pubid/plateau/identifiers/handbook.rb +10 -2
- data/lib/pubid/plateau/pubid_urn_schema.rb +1 -0
- data/lib/pubid/plateau/renderer.rb +6 -1
- data/lib/pubid/sae/CLAUDE.md +11 -2
- data/lib/pubid/version.rb +1 -1
- metadata +4 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Pubid
|
|
6
|
+
module Iho
|
|
7
|
+
# The S-4 reserved specification registry
|
|
8
|
+
# (data/iho/s4-reserved-specs.yaml): the "NOT CURRENTLY USED"
|
|
9
|
+
# numbers, edition-keyed (owner rulings of 2026-11-11, issue #691).
|
|
10
|
+
# {Reserved?} answers from this table — a reserved number parses and
|
|
11
|
+
# resolves AS RESERVED, never invalid.
|
|
12
|
+
module ReservedSpecs
|
|
13
|
+
DATA_PATH =
|
|
14
|
+
File.expand_path("../../../data/iho/s4-reserved-specs.yaml", __dir__)
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# True when the part-lettered spec number is reserved in the
|
|
18
|
+
# given S-4 edition. An edition-less reference is reserved when
|
|
19
|
+
# ANY seeded edition reserves the number: the number is out of
|
|
20
|
+
# circulation in every edition that names it.
|
|
21
|
+
def reserved?(edition, part, spec)
|
|
22
|
+
return false unless part && spec
|
|
23
|
+
|
|
24
|
+
if edition
|
|
25
|
+
specs_for(edition, part).include?(spec)
|
|
26
|
+
else
|
|
27
|
+
registry.values.any? do |parts|
|
|
28
|
+
specs_for_parts(parts, part).include?(spec)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The edition-keyed registry: edition => part letter => spec
|
|
34
|
+
# numbers.
|
|
35
|
+
def registry
|
|
36
|
+
@registry ||=
|
|
37
|
+
(YAML.safe_load_file(DATA_PATH) || {}).transform_values do |parts|
|
|
38
|
+
parts.transform_values { |specs| Array(specs) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def specs_for(edition, part)
|
|
45
|
+
specs_for_parts(registry.fetch(edition, {}), part)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def specs_for_parts(parts, part)
|
|
49
|
+
parts.fetch(part, [])
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -31,6 +31,7 @@ module Pubid
|
|
|
31
31
|
# - urn:iho:m:2:sup-1 IHO M-2 Sup 1
|
|
32
32
|
# - urn:iho:p:5:vol-2024 IHO P-5 Vol. 2024
|
|
33
33
|
# - urn:iho:p:1:vol-30:issue-2 IHO P-1 Vol. 30 (2)
|
|
34
|
+
# - urn:iho:s:4:ed-4.10.0:part-B:spec-401 IHO S-4 4.10.0 B-401
|
|
34
35
|
class UrnGenerator
|
|
35
36
|
attr_reader :identifier
|
|
36
37
|
|
|
@@ -49,6 +50,11 @@ module Pubid
|
|
|
49
50
|
[:supplement, "sup"], [:supplement_edition, "ed"],
|
|
50
51
|
[:supplement_year, "year"],
|
|
51
52
|
[:part, "part"], [:part_edition, "ed"], [:part_year, "year"],
|
|
53
|
+
# the S-4 spec citation: a child of its part (the citation
|
|
54
|
+
# letter rides the part- atom), the dotted sub and the
|
|
55
|
+
# parenthesized variant its own children. The SECTION is NOT
|
|
56
|
+
# emitted — derivable from the spec's hundreds digit.
|
|
57
|
+
[:spec, "spec"], [:spec_sub, "specsub"], [:spec_variant, "specvar"],
|
|
52
58
|
[:annex, "annex"], [:annex_edition, "ed"], [:annex_year, "year"],
|
|
53
59
|
[:appendix, "appendix"], [:appendix_subnumber, nil],
|
|
54
60
|
[:appendix_edition, "ed"], [:appendix_year, "year"],
|
data/lib/pubid/iho/urn_parser.rb
CHANGED
|
@@ -7,8 +7,8 @@ module Pubid
|
|
|
7
7
|
# the segment table lives in the grammar header and the flavor notes):
|
|
8
8
|
#
|
|
9
9
|
# urn:iho:<type>:<number>[:ed-<x.y.z>][:sup-<n>][:part-<X>]
|
|
10
|
-
# [:
|
|
11
|
-
# [:year-<yyyy>]
|
|
10
|
+
# [:spec-<n>[:specsub-<n>][:specvar-<X>]][:annex-<A>]
|
|
11
|
+
# [:appendix-<B>][:<sub>][:vol-<n>][:issue-<n>][:year-<yyyy>]
|
|
12
12
|
#
|
|
13
13
|
# Segment discipline (mirrors the ISO URN parser's strictness):
|
|
14
14
|
# - the docnumber occupies every leading bare number segment up to
|
|
@@ -22,7 +22,11 @@ module Pubid
|
|
|
22
22
|
# directly after its appendix segment (a year is always
|
|
23
23
|
# marker-tagged, so a bare digit there can only be the subnumber);
|
|
24
24
|
# - the issue is allowed only beside its volume and validated as
|
|
25
|
-
# digits
|
|
25
|
+
# digits;
|
|
26
|
+
# - the S-4 spec atom is a child of its part (the citation letter
|
|
27
|
+
# rides the part- atom): specsub-/specvar- only after spec-, and
|
|
28
|
+
# no ed-/year- after a spec node (the spec rides its publication's
|
|
29
|
+
# edition; the section is derivable, never emitted).
|
|
26
30
|
#
|
|
27
31
|
# Legacy 2.0.x spellings remain decodable but are never emitted:
|
|
28
32
|
# the bare a.b.c edition segment, the dotted keys part./annex./
|
|
@@ -38,6 +42,7 @@ module Pubid
|
|
|
38
42
|
# - urn:iho:p:5:vol-2024 → IHO P-5 Vol. 2024
|
|
39
43
|
# - urn:iho:p:1:vol-30:issue-2 → IHO P-1 Vol. 30 (2)
|
|
40
44
|
# - urn:iho:s:158:100 → IHO S-158:100
|
|
45
|
+
# - urn:iho:s:4:ed-4.10.0:part-B:spec-401 → IHO S-4 4.10.0 B-401
|
|
41
46
|
class UrnParser < Pubid::UrnParser::Base
|
|
42
47
|
TYPE_LETTERS = %w[s p m b c].freeze
|
|
43
48
|
|
|
@@ -50,6 +55,9 @@ module Pubid
|
|
|
50
55
|
"ed" => [:edition, /\A\d+\.\d+\.\d+\z/],
|
|
51
56
|
"sup" => [:supplement, /\A\d+\z/],
|
|
52
57
|
"part" => [:part, /\A(?:\d+[a-zA-Z]*|[A-Z]|[SPMBC]-\d+)\z/],
|
|
58
|
+
"spec" => [:spec, /\A[1-6]\d\d\z/],
|
|
59
|
+
"specsub" => [:spec_sub, /\A\d+\z/],
|
|
60
|
+
"specvar" => [:spec_variant, /\A[A-Z]+\z/],
|
|
53
61
|
"annex" => [:annex, /\A[A-Z]\z/],
|
|
54
62
|
"appendix" => [:appendix, /\A(?:[A-Z]|\d+)\z/],
|
|
55
63
|
"vol" => [:volume, /\A\d+\z/],
|
|
@@ -63,6 +71,12 @@ module Pubid
|
|
|
63
71
|
# follow (the component-edition duality).
|
|
64
72
|
POSITIONAL_MARKERS = %i[edition year].freeze
|
|
65
73
|
|
|
74
|
+
# The S-4 spec node and its children: specsub-/specvar- are only
|
|
75
|
+
# allowed after their parent, and an S-4 spec rides its
|
|
76
|
+
# publication's edition — no ed-/year- inside a spec.
|
|
77
|
+
SPEC_NODES = %i[spec spec_sub spec_variant].freeze
|
|
78
|
+
SPEC_PARENTS = %i[spec spec_sub].freeze
|
|
79
|
+
|
|
66
80
|
# Legacy dotted keys (deprecated; decodable, never emitted).
|
|
67
81
|
# "ap" is the pre-positional appendix tag; "suppl" the pre-positional
|
|
68
82
|
# supplement tag; "y" the pre-positional year tag — under the
|
|
@@ -97,11 +111,22 @@ module Pubid
|
|
|
97
111
|
# own edition).
|
|
98
112
|
ctx = { component: nil, edition_owner: nil }
|
|
99
113
|
consume_segments!(attrs, segments, ctx, urn)
|
|
100
|
-
|
|
114
|
+
build_identifier({ type: type_token.upcase, **attrs }, urn)
|
|
101
115
|
end
|
|
102
116
|
|
|
103
117
|
private
|
|
104
118
|
|
|
119
|
+
# The semantic validations the builder applies to the decoded shape
|
|
120
|
+
# (the S-4 anchor, the citation letter) are URN validation at this
|
|
121
|
+
# layer: a URN that asserts a citation it cannot carry is a
|
|
122
|
+
# malformed URN, and the raw ArgumentError stays as the cause.
|
|
123
|
+
def build_identifier(hash, urn)
|
|
124
|
+
Builder.build(hash)
|
|
125
|
+
rescue ArgumentError => e
|
|
126
|
+
raise Pubid::Errors::UrnParseError,
|
|
127
|
+
"#{e.message} in #{urn.inspect}"
|
|
128
|
+
end
|
|
129
|
+
|
|
105
130
|
def read_type!(segments, urn)
|
|
106
131
|
type_token = segments.shift.to_s.downcase
|
|
107
132
|
return type_token if TYPE_LETTERS.include?(type_token)
|
|
@@ -159,10 +184,9 @@ module Pubid
|
|
|
159
184
|
marker, value = MARKER_RE.match(segment).captures
|
|
160
185
|
key, pattern = MARKERS.fetch(marker)
|
|
161
186
|
raise_invalid!(marker, value, urn) unless value.match?(pattern)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
end
|
|
187
|
+
assert_spec_parent!(key, ctx, urn)
|
|
188
|
+
return consume_positional!(attrs, key, value, ctx, urn) if
|
|
189
|
+
POSITIONAL_MARKERS.include?(key)
|
|
166
190
|
|
|
167
191
|
store!(attrs, key, value, urn)
|
|
168
192
|
ctx[:component] = key
|
|
@@ -170,6 +194,16 @@ module Pubid
|
|
|
170
194
|
consume_subnumber!(attrs, key, segments, ctx, urn)
|
|
171
195
|
end
|
|
172
196
|
|
|
197
|
+
# specsub-/specvar- are children of the spec atom — allowed only
|
|
198
|
+
# in their parent's slot (the SEGMENTS table in the generator).
|
|
199
|
+
def assert_spec_parent!(key, ctx, urn)
|
|
200
|
+
return unless %i[spec_sub spec_variant].include?(key)
|
|
201
|
+
return if SPEC_PARENTS.include?(ctx[:component])
|
|
202
|
+
|
|
203
|
+
raise Pubid::Errors::UrnParseError,
|
|
204
|
+
"#{key}- is only allowed after spec- in #{urn.inspect}"
|
|
205
|
+
end
|
|
206
|
+
|
|
173
207
|
def raise_invalid!(marker, value, urn)
|
|
174
208
|
raise Pubid::Errors::UrnParseError,
|
|
175
209
|
"Invalid #{marker} value #{value.inspect} in #{urn.inspect}"
|
|
@@ -177,8 +211,15 @@ module Pubid
|
|
|
177
211
|
|
|
178
212
|
# ed- and year- are POSITIONAL markers (see the marker docs): the
|
|
179
213
|
# edition attaches to the component it follows, the year to the
|
|
180
|
-
# edition owner.
|
|
214
|
+
# edition owner. An S-4 spec carries neither — it rides its
|
|
215
|
+
# publication's edition.
|
|
181
216
|
def consume_positional!(attrs, key, value, ctx, urn)
|
|
217
|
+
if SPEC_NODES.include?(ctx[:component])
|
|
218
|
+
raise Pubid::Errors::UrnParseError,
|
|
219
|
+
"an S-4 spec rides its publication's edition: #{key}- " \
|
|
220
|
+
"cannot follow a spec segment in #{urn.inspect}"
|
|
221
|
+
end
|
|
222
|
+
|
|
182
223
|
if key == :edition
|
|
183
224
|
return store_component_scoped!(attrs, :edition, value, ctx, urn)
|
|
184
225
|
end
|
data/lib/pubid/iho.rb
CHANGED
|
@@ -12,6 +12,7 @@ module Pubid
|
|
|
12
12
|
autoload :Identifier, "#{__dir__}/iho/identifiers/base"
|
|
13
13
|
autoload :Identifiers, "#{__dir__}/iho/identifiers"
|
|
14
14
|
autoload :Renderer, "#{__dir__}/iho/renderer"
|
|
15
|
+
autoload :ReservedSpecs, "#{__dir__}/iho/reserved_specs"
|
|
15
16
|
autoload :UrnGenerator, "#{__dir__}/iho/urn_generator"
|
|
16
17
|
autoload :UrnParser, "#{__dir__}/iho/urn_parser"
|
|
17
18
|
autoload :PubidUrnSchema, "#{__dir__}/iho/pubid_urn_schema"
|
data/lib/pubid/nist/CLAUDE.md
CHANGED
|
@@ -135,3 +135,14 @@ Read this before you change `lib/pubid/nist/` or `spec/pubid/nist/`. The root
|
|
|
135
135
|
`spec/pubid/nist/bare_series_reference_spec.rb`; the corpus round froze
|
|
136
136
|
the six rows (`NBS CS`, `NBS CSM`, `NBS CIS`, `NBS CS-E`, `NIST PS`,
|
|
137
137
|
`NIST DCI`) into the testsuite with the gem twin in lockstep.
|
|
138
|
+
|
|
139
|
+
- **"FIPS PUB <number>" is a preprocessor alias, not a grammar face
|
|
140
|
+
(pubid#697).** NIST prints the series as "FIPS PUB <number>" but the
|
|
141
|
+
grammar reads "NIST FIPS <number>", so `normalize_fips_pub_alias!`
|
|
142
|
+
(head-anchored, immediately after the publisher/series stage) rewrites
|
|
143
|
+
the printed prefix and the parse lands on exactly the canonical model —
|
|
144
|
+
hash-, render-, URN- and MR-identical to "NIST FIPS <number>". The
|
|
145
|
+
hyphenated numbers and the revision forms the grammar models
|
|
146
|
+
("FIPS PUB 199 Rev. 1" with its `original_prefix` facet, compact
|
|
147
|
+
"rev1") ride the prefix alias; bare "FIPS <number>"
|
|
148
|
+
(publisher_was_parsed: false) and NBS FIPS are untouched.
|
|
@@ -68,6 +68,7 @@ module Pubid
|
|
|
68
68
|
end
|
|
69
69
|
normalize_spurious_u_suffix!
|
|
70
70
|
normalize_publisher_and_series!
|
|
71
|
+
normalize_fips_pub_alias!
|
|
71
72
|
normalize_lcirc_supplement_contexts!
|
|
72
73
|
normalize_revision_spacing!
|
|
73
74
|
normalize_letter_suffix_casing!
|
|
@@ -124,6 +125,21 @@ module Pubid
|
|
|
124
125
|
@cleaned = @cleaned.gsub(/\bLC\b(?!IRC)/, "LCIRC")
|
|
125
126
|
end
|
|
126
127
|
|
|
128
|
+
# The FIPS series' own printed face: NIST prints the series as
|
|
129
|
+
# "FIPS PUB <number>" (document cover and catalogue style), so the
|
|
130
|
+
# grammar accepts the publisher-prefixed canonical form only through
|
|
131
|
+
# this alias (pubid#697). Rewrites the prefix to "NIST FIPS", the
|
|
132
|
+
# spelling the grammar already reads, and the printed-form parse
|
|
133
|
+
# therefore lands on exactly the model "NIST FIPS <number>" builds
|
|
134
|
+
# (same to_hash, same render, revision spellings like
|
|
135
|
+
# "FIPS PUB 199 Rev. 1" included). Anchored at the head and run
|
|
136
|
+
# before every other stage so later digit/marker stages see the
|
|
137
|
+
# canonical face; "FIPS" alone (publisher_was_parsed: false) is
|
|
138
|
+
# untouched.
|
|
139
|
+
def normalize_fips_pub_alias!
|
|
140
|
+
@cleaned = @cleaned.sub(/\AFIPS PUB\b/i, "NIST FIPS")
|
|
141
|
+
end
|
|
142
|
+
|
|
127
143
|
# LCIRC series: combine with NBS prefix when a supplement marker
|
|
128
144
|
# follows, and convert MR-format dots to spaces so the grammar's
|
|
129
145
|
# circ_supplement_identifier rule can match.
|
data/lib/pubid/plateau/CLAUDE.md
CHANGED
|
@@ -11,3 +11,38 @@ Integer, so its core is `:int` (`core: [["number", :int]]`): `#00` is the core
|
|
|
11
11
|
`0`, and `00` in a URN is not canonical. The Annex type has no `number`; it
|
|
12
12
|
has no core, its document comes first along `base`, and its `letter` is an
|
|
13
13
|
atom. No input makes an Annex today, so the spec builds one by hand.
|
|
14
|
+
|
|
15
|
+
## Handbook volume label (本編 / 本編+付録)
|
|
16
|
+
|
|
17
|
+
MLIT publishes Handbook #00 edition 5.0 twice on one date: `第5.0版 本編`
|
|
18
|
+
(main volume) and `第5.0版 本編+付録` (main volume and appendix, one PDF);
|
|
19
|
+
edition 6.0 only as `本編+付録`. `Identifiers::Handbook#volume` keeps that
|
|
20
|
+
label as Japanese text, as `edition` keeps `第X.Y版`. The grammar accepts it
|
|
21
|
+
only after an edition, separated by an ASCII or a full-width (U+3000) space,
|
|
22
|
+
with `+` or ASCII `+`; the builder normalizes `+` to `+`, and `to_s` writes
|
|
23
|
+
one ASCII space before it. The renderer prints the volume only after an
|
|
24
|
+
edition, so `exclude(:edition)` still renders a string that parses.
|
|
25
|
+
`volume` is in `==` and is `subset_strict`, so `#00 第5.0版` matches neither
|
|
26
|
+
labelled publication. A nil volume leaves no key in `to_hash`, so the
|
|
27
|
+
existing index-v2 rows keep their hash. The pubid-scheme URN carries it as
|
|
28
|
+
the `vol-` atom; `to_urn` is unchanged (it has no edition either). Locked by
|
|
29
|
+
`spec/pubid/plateau/handbook_volume_spec.rb`.
|
|
30
|
+
|
|
31
|
+
Known gaps, not caused by this change: a Technical Report drops an edition
|
|
32
|
+
and a volume without a diagnostic (the `handbook` rule matches any
|
|
33
|
+
`doc_type`), and `to_mr_string` raises for every Handbook with an edition
|
|
34
|
+
(the base `mr_edition` calls `edition.number` on a String).
|
|
35
|
+
|
|
36
|
+
## The grammar is baked here ahead of pubid-grammar
|
|
37
|
+
|
|
38
|
+
`data/parg/plateau.json` was compiled here from its own embedded `source`
|
|
39
|
+
plus the volume rule, at parsanol **1.3.114** (the version of the earlier
|
|
40
|
+
bake; force it with `gem "parsanol", "1.3.114"`, as
|
|
41
|
+
`pubid-grammar/scripts/bake-one.rb` does, because the binstub resolves the
|
|
42
|
+
latest installed gem). Until pubid-grammar takes hand-off
|
|
43
|
+
`pubid__pubid-grammar__plateau-handbook-volume`, its `grammars/plateau.parg`
|
|
44
|
+
is the OLD grammar. To change the grammar, extract the source with
|
|
45
|
+
`jq -j .source data/parg/plateau.json` (`-j`: the source has no trailing
|
|
46
|
+
newline, and one extra byte changes the checksum), edit, re-bake, then run
|
|
47
|
+
`bundle exec parsanol parg test data/parg/plateau.json`. Read the `.parg`
|
|
48
|
+
file as UTF-8: under a US-ASCII locale the parsanol lexer raises on `第`.
|
|
@@ -34,6 +34,10 @@ module Pubid
|
|
|
34
34
|
if parsed[:edition] && klass == Identifiers::Handbook
|
|
35
35
|
params[:edition] = parsed[:edition].to_s
|
|
36
36
|
end
|
|
37
|
+
# The grammar accepts an ASCII "+"; MLIT prints the full-width "+".
|
|
38
|
+
if parsed[:volume] && klass == Identifiers::Handbook
|
|
39
|
+
params[:volume] = parsed[:volume].to_s.tr("+", "+")
|
|
40
|
+
end
|
|
37
41
|
|
|
38
42
|
klass.new(**params)
|
|
39
43
|
end
|
|
@@ -4,10 +4,18 @@ module Pubid
|
|
|
4
4
|
module Plateau
|
|
5
5
|
module Identifiers
|
|
6
6
|
# PLATEAU Handbook
|
|
7
|
-
# Format: PLATEAU Handbook #NN[-annex] [第X.Y版]
|
|
7
|
+
# Format: PLATEAU Handbook #NN[-annex] [第X.Y版[ volume]]
|
|
8
8
|
# Example: PLATEAU Handbook #00 第1.0版
|
|
9
|
+
# Example: PLATEAU Handbook #00 第5.0版 本編+付録
|
|
9
10
|
class Handbook < Identifier
|
|
10
11
|
attribute :edition, :string, default: -> {}
|
|
12
|
+
# The volume label MLIT prints after the edition: "本編" (the main
|
|
13
|
+
# volume alone) or "本編+付録" (main volume and appendix, one PDF).
|
|
14
|
+
attribute :volume, :string, default: -> {}
|
|
15
|
+
|
|
16
|
+
# A nil `volume` means the publication has no volume label:
|
|
17
|
+
# `#00 第5.0版` does not subsume `#00 第5.0版 本編+付録`.
|
|
18
|
+
subset_strict :volume
|
|
11
19
|
|
|
12
20
|
def type_string
|
|
13
21
|
"Handbook"
|
|
@@ -20,7 +28,7 @@ module Pubid
|
|
|
20
28
|
def ==(other)
|
|
21
29
|
return false unless other.is_a?(Handbook)
|
|
22
30
|
|
|
23
|
-
super && edition == other.edition
|
|
31
|
+
super && edition == other.edition && volume == other.volume
|
|
24
32
|
end
|
|
25
33
|
end
|
|
26
34
|
end
|
|
@@ -39,7 +39,12 @@ module Pubid
|
|
|
39
39
|
|
|
40
40
|
def render_handbook(id)
|
|
41
41
|
result = "#{id.publisher} #{id.type_string} #{id.formatted_number}#{id.formatted_annex}"
|
|
42
|
-
result
|
|
42
|
+
return result unless id.edition
|
|
43
|
+
|
|
44
|
+
# The grammar reads a volume label only after an edition, so a
|
|
45
|
+
# volume without one (e.g. after exclude(:edition)) is not printed.
|
|
46
|
+
result += " #{id.formatted_edition}"
|
|
47
|
+
result += " #{id.volume}" if id.volume
|
|
43
48
|
result
|
|
44
49
|
end
|
|
45
50
|
|
data/lib/pubid/sae/CLAUDE.md
CHANGED
|
@@ -12,8 +12,17 @@ Measured on the full deposit list (2026-11-04, 47,692 DOIs): **46,726
|
|
|
12
12
|
parse**, and every parsed code round-trips through `to_s`, `from_hash` and
|
|
13
13
|
the URN, with **0 `to_hash` collisions**. The 966 that do not parse are:
|
|
14
14
|
|
|
15
|
-
- **791 CP\*** (`CPGM`, `CPFD`, `CPKW`, …):
|
|
16
|
-
|
|
15
|
+
- **791 CP\*** (`CPGM`, `CPFD`, `CPKW`, …): SAE "Certified Power" data
|
|
16
|
+
products — engine power/torque data certified under SAE J1349, one per
|
|
17
|
+
vehicle and level (`CPGM2_15LFXCOCA`: "J1349 Certified Power Engine Data
|
|
18
|
+
for GM as used in 2015 LFX Colorado/Canyon - Level 2"). The letters after
|
|
19
|
+
`CP` name the manufacturer (`GM`, `FD` Ford, `FC` FCA, …). They are data
|
|
20
|
+
products, not standards, and stay out of `TYPES` by decision (pubid#686).
|
|
21
|
+
`SAE CPGM2016` in pubid#686 is not a deposit: it is the synthetic DOI of
|
|
22
|
+
relaton's "reports a record pubid cannot parse" spec
|
|
23
|
+
(`spec/sae/relaton/sae/data_fetcher_spec.rb`), which, with
|
|
24
|
+
`bibliography_spec.rb` and `docs/sae.md`, relies on it failing to parse.
|
|
25
|
+
`spec/pubid/sae/crossref_coverage_spec.rb` pins the rejection.
|
|
17
26
|
- **175 one-offs**, none more than 32 of a kind: `SUP1` supplements
|
|
18
27
|
(`AS4468SUP1A`, `ARP4737SUP1`), language tails (`AS9101F_PTBR`,
|
|
19
28
|
`IA9137_DEDE`), `J2534-2/10_5_00`, `_4Q2012`, type-less `SAE6857`,
|
data/lib/pubid/version.rb
CHANGED
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.
|
|
4
|
+
version: 2.0.21
|
|
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-
|
|
11
|
+
date: 2026-11-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|
|
@@ -71,6 +71,7 @@ files:
|
|
|
71
71
|
- data/iec/stage-codes.md
|
|
72
72
|
- data/iec/update_codes.yaml
|
|
73
73
|
- data/ieee/update_codes.yaml
|
|
74
|
+
- data/iho/s4-reserved-specs.yaml
|
|
74
75
|
- data/iso/update_codes.yaml
|
|
75
76
|
- data/nist/series.yaml
|
|
76
77
|
- data/nist/stages.yaml
|
|
@@ -733,6 +734,7 @@ files:
|
|
|
733
734
|
- lib/pubid/iho/identifiers/standard.rb
|
|
734
735
|
- lib/pubid/iho/pubid_urn_schema.rb
|
|
735
736
|
- lib/pubid/iho/renderer.rb
|
|
737
|
+
- lib/pubid/iho/reserved_specs.rb
|
|
736
738
|
- lib/pubid/iho/urn_generator.rb
|
|
737
739
|
- lib/pubid/iho/urn_parser.rb
|
|
738
740
|
- lib/pubid/isbn.rb
|