pubid 2.0.0.pre.alpha.1 → 2.0.0.pre.alpha.2
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/nist/update_codes.yaml +2 -0
- data/lib/pubid/amca/identifier.rb +39 -0
- data/lib/pubid/ansi/identifier.rb +42 -0
- data/lib/pubid/api/identifier.rb +47 -0
- data/lib/pubid/ashrae/identifier.rb +39 -0
- data/lib/pubid/asme/identifier.rb +46 -0
- data/lib/pubid/astm/identifier.rb +77 -0
- data/lib/pubid/bsi/identifier.rb +60 -0
- data/lib/pubid/ccsds/identifier.rb +68 -0
- data/lib/pubid/ccsds/identifiers/base.rb +11 -0
- data/lib/pubid/ccsds/single_identifier.rb +4 -1
- data/lib/pubid/cen_cenelec/identifier.rb +37 -0
- data/lib/pubid/cie/identifier.rb +53 -0
- data/lib/pubid/components/factory.rb +50 -0
- data/lib/pubid/components/typed_stage.rb +4 -0
- data/lib/pubid/components.rb +1 -0
- data/lib/pubid/csa/identifier.rb +56 -0
- data/lib/pubid/etsi/identifier.rb +43 -0
- data/lib/pubid/identifier.rb +8 -1
- data/lib/pubid/idf/identifier.rb +60 -0
- data/lib/pubid/iec/builder.rb +2 -1
- data/lib/pubid/iec/components/code.rb +2 -1
- data/lib/pubid/iec/components/publisher.rb +2 -1
- data/lib/pubid/iec/identifier.rb +235 -0
- data/lib/pubid/iec/identifiers/base.rb +4 -0
- data/lib/pubid/iec/identifiers/consolidated_identifier.rb +0 -4
- data/lib/pubid/iec/identifiers/fragment_identifier.rb +0 -4
- data/lib/pubid/iec/identifiers/sheet_identifier.rb +0 -4
- data/lib/pubid/iec/identifiers/vap_identifier.rb +0 -4
- data/lib/pubid/iec/parser.rb +7 -2
- data/lib/pubid/iec/urn_generator.rb +57 -171
- data/lib/pubid/iec/urn_parser.rb +53 -252
- data/lib/pubid/ieee/identifier.rb +41 -0
- data/lib/pubid/iho/identifier.rb +42 -0
- data/lib/pubid/iho/identifiers/base.rb +1 -1
- data/lib/pubid/iho/identifiers/bibliographic.rb +0 -4
- data/lib/pubid/iho/identifiers/circular_letter.rb +0 -4
- data/lib/pubid/iho/identifiers/miscellaneous.rb +0 -4
- data/lib/pubid/iho/identifiers/publication.rb +0 -4
- data/lib/pubid/iho/identifiers/standard.rb +0 -4
- data/lib/pubid/iho/urn_generator.rb +1 -1
- data/lib/pubid/iso/builder.rb +5 -1
- data/lib/pubid/iso/identifier.rb +261 -0
- data/lib/pubid/iso/parser.rb +4 -2
- data/lib/pubid/iso/scheme.rb +6 -0
- data/lib/pubid/iso/single_identifier.rb +6 -3
- data/lib/pubid/iso/urn_generator.rb +17 -3
- data/lib/pubid/iso/urn_parser.rb +16 -2
- data/lib/pubid/itu/identifier.rb +87 -22
- data/lib/pubid/jcgm/identifier.rb +43 -0
- data/lib/pubid/jis/identifier.rb +43 -0
- data/lib/pubid/nist/builder.rb +174 -5
- data/lib/pubid/nist/components/edition.rb +16 -0
- data/lib/pubid/nist/components/supplement.rb +88 -21
- data/lib/pubid/nist/identifier.rb +62 -0
- data/lib/pubid/nist/identifiers/base.rb +103 -24
- data/lib/pubid/nist/identifiers/circular_supplement.rb +1 -1
- data/lib/pubid/nist/identifiers/crpl_report.rb +1 -4
- data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +10 -0
- data/lib/pubid/nist/identifiers/report.rb +1 -2
- data/lib/pubid/nist/parser.rb +36 -3
- data/lib/pubid/nist/supplement_identifier.rb +8 -24
- data/lib/pubid/nist/urn_generator.rb +14 -8
- data/lib/pubid/nist.rb +1 -0
- data/lib/pubid/oiml/identifier.rb +50 -0
- data/lib/pubid/plateau/identifier.rb +57 -0
- data/lib/pubid/plateau.rb +1 -0
- data/lib/pubid/renderers/base.rb +34 -0
- data/lib/pubid/renderers/directives_renderer.rb +13 -14
- data/lib/pubid/renderers/guide_renderer.rb +5 -1
- data/lib/pubid/renderers/human_readable.rb +20 -8
- data/lib/pubid/renderers/iwa_renderer.rb +5 -1
- data/lib/pubid/renderers/supplement_renderer.rb +4 -1
- data/lib/pubid/rendering/context.rb +5 -2
- data/lib/pubid/sae/identifier.rb +23 -0
- data/lib/pubid/scheme.rb +12 -0
- data/lib/pubid/version.rb +1 -1
- metadata +5 -2
|
@@ -26,7 +26,7 @@ module Pubid
|
|
|
26
26
|
# Render the identifier as a string in canonical IHO form.
|
|
27
27
|
# @return [String]
|
|
28
28
|
def to_s
|
|
29
|
-
letter =
|
|
29
|
+
letter = self.class.type[:short]
|
|
30
30
|
rendered = "#{publisher} #{letter}-#{code}"
|
|
31
31
|
rendered << " Ap. #{appendix}" if appendix
|
|
32
32
|
rendered << " Part #{part}" if part
|
|
@@ -15,7 +15,7 @@ module Pubid
|
|
|
15
15
|
|
|
16
16
|
def generate
|
|
17
17
|
parts = ["urn", "iho"]
|
|
18
|
-
parts << identifier.type[:short].to_s.downcase
|
|
18
|
+
parts << identifier.class.type[:short].to_s.downcase
|
|
19
19
|
parts << identifier.code.to_s
|
|
20
20
|
parts << "ap.#{identifier.appendix}" if identifier.appendix
|
|
21
21
|
parts << "part.#{identifier.part}" if identifier.part
|
data/lib/pubid/iso/builder.rb
CHANGED
|
@@ -125,7 +125,11 @@ module Pubid
|
|
|
125
125
|
copublisher: value[:copublisher],
|
|
126
126
|
)
|
|
127
127
|
else
|
|
128
|
-
|
|
128
|
+
# Default copublisher to [] (not nil) so a bare-string publisher
|
|
129
|
+
# (e.g. a TC document, which skips the copublisher merge) matches
|
|
130
|
+
# the [] convention used by copublisher-merged parses and by
|
|
131
|
+
# Identifier.create. Otherwise equality fails on [] vs nil.
|
|
132
|
+
Pubid::Iso::Components::Publisher.new(publisher: value, copublisher: [])
|
|
129
133
|
end
|
|
130
134
|
|
|
131
135
|
when :copublishers
|
data/lib/pubid/iso/identifier.rb
CHANGED
|
@@ -64,6 +64,267 @@ module Pubid
|
|
|
64
64
|
Pubid::Iso::Builder.new(Pubid::Iso::Scheme).build(parsed)
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
|
+
|
|
68
|
+
# Factory mirroring pubid 1.x's `Pubid::Iso::Identifier.create` API.
|
|
69
|
+
#
|
|
70
|
+
# Accepts 1.x-style primitive kwargs and dispatches to the correct
|
|
71
|
+
# 2.x `Identifiers::*` subclass via {Pubid::Iso::Scheme}. Coerces
|
|
72
|
+
# primitives into ISO-specific Component objects.
|
|
73
|
+
#
|
|
74
|
+
# Dispatch rules:
|
|
75
|
+
# * `type:` (e.g. `:tr`, `:amd`) → lookup via Scheme
|
|
76
|
+
# * else `stage:` (e.g. `"DIS"`, `"AMD"`) → lookup via Scheme
|
|
77
|
+
# * else → InternationalStandard
|
|
78
|
+
#
|
|
79
|
+
# @param type [Symbol, String, nil] type key (`:is`, `:tr`, `:amd`, …)
|
|
80
|
+
# @param stage [String, Symbol, nil] typed-stage abbreviation
|
|
81
|
+
# @param opts [Hash] remaining attribute primitives:
|
|
82
|
+
# :publisher (String), :number, :part, :subpart, :year, :edition,
|
|
83
|
+
# :language
|
|
84
|
+
# @return [Pubid::Iso::Identifier]
|
|
85
|
+
def self.create(type: nil, stage: nil, base: nil, **opts)
|
|
86
|
+
# A bundled directive (e.g. "ISO/IEC DIR 1 + IEC SUP") is stored by the
|
|
87
|
+
# 1.x index as the base document's fields plus a nested joint_document
|
|
88
|
+
# (or a supplements array). The 2.x model is a separate
|
|
89
|
+
# BundledIdentifier; build that so .create round-trips parse.
|
|
90
|
+
if opts[:joint_document] || opts[:supplements]
|
|
91
|
+
return build_bundled(type: type, stage: stage, base: base, **opts)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
klass = resolve_create_class(type: type, stage: stage, base: base)
|
|
95
|
+
attrs = coerce_create_attrs(opts)
|
|
96
|
+
ts = resolve_create_typed_stage(klass, stage)
|
|
97
|
+
if ts
|
|
98
|
+
# dup the (shared) TYPED_STAGES element before tweaking, and set
|
|
99
|
+
# original_abbr to the canonical abbr so rendering matches a
|
|
100
|
+
# parsed identifier (parse records the spelled abbr, e.g. "Amd"
|
|
101
|
+
# not the upcased short_abbr "AMD").
|
|
102
|
+
ts = ts.dup
|
|
103
|
+
ts.original_abbr ||= Array(ts.abbr).first&.to_s
|
|
104
|
+
attrs[:typed_stage] = ts
|
|
105
|
+
# Parse fills `type` and `stage` Components derived from
|
|
106
|
+
# typed_stage; mirror that here so .create round-trips through
|
|
107
|
+
# Pubid::Identifier#== with a parsed identifier.
|
|
108
|
+
attrs[:type] ||= ::Pubid::Components::Type.new(
|
|
109
|
+
name: ts.name,
|
|
110
|
+
abbr: Array(ts.abbr).first.to_s,
|
|
111
|
+
type_code: ts.type_code&.to_s,
|
|
112
|
+
)
|
|
113
|
+
attrs[:stage] ||= ::Pubid::Components::Stage.new(
|
|
114
|
+
name: ts.name,
|
|
115
|
+
stage_code: ts.stage_code&.to_s,
|
|
116
|
+
abbr: Array(ts.abbr).first.to_s,
|
|
117
|
+
harmonized_stages: Array(ts.harmonized_stages),
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
# Build the base_identifier whenever a `base:` is supplied, regardless
|
|
121
|
+
# of whether the resolved class is registered as a supplement (e.g.
|
|
122
|
+
# DirectivesSupplement holds a base but is not in
|
|
123
|
+
# Scheme#supplement_identifiers). Only classes that *require* a base
|
|
124
|
+
# and were given none raise.
|
|
125
|
+
if base
|
|
126
|
+
attrs[:base_identifier] = build_base_identifier(base)
|
|
127
|
+
elsif supplement_klass?(klass)
|
|
128
|
+
raise ArgumentError, "#{klass} requires a base: identifier"
|
|
129
|
+
end
|
|
130
|
+
# For a DirectivesSupplement the top-level `publisher:` names the
|
|
131
|
+
# supplement's own publisher ("… ISO SUP"), not the document's — the
|
|
132
|
+
# document publisher lives on the base. Mirror parse, which records it
|
|
133
|
+
# as `supplement_publisher`.
|
|
134
|
+
if klass <= Identifiers::DirectivesSupplement && attrs.key?(:publisher)
|
|
135
|
+
attrs[:supplement_publisher] = attrs.delete(:publisher)
|
|
136
|
+
attrs.delete(:copublishers)
|
|
137
|
+
end
|
|
138
|
+
klass.new(**attrs)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Build a BundledIdentifier (base document + supplements) from the 1.x
|
|
142
|
+
# index shape: the top-level fields are the base document, and each
|
|
143
|
+
# joint_document/supplements entry is a joined supplement.
|
|
144
|
+
def self.build_bundled(type:, stage:, base:, **opts)
|
|
145
|
+
raw = opts.delete(:joint_document) || opts.delete(:supplements)
|
|
146
|
+
entries = raw.is_a?(Array) ? raw : [raw]
|
|
147
|
+
base_document = create(type: type, stage: stage, base: base, **opts)
|
|
148
|
+
supplements = entries.map { |j| build_joint_supplement(j) }
|
|
149
|
+
BundledIdentifier.new(base_document: base_document,
|
|
150
|
+
supplements: supplements)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Map a 1.x joint_document hash to a 2.x supplement matching parse. The
|
|
154
|
+
# index nests the supplement's publisher under joint_document.base.publisher
|
|
155
|
+
# (e.g. "IEC" for "+ IEC SUP"), while parse records it as the supplement's
|
|
156
|
+
# own publisher; move it and drop the nested base + empty top publisher.
|
|
157
|
+
def self.build_joint_supplement(joint)
|
|
158
|
+
j = joint.transform_keys(&:to_sym)
|
|
159
|
+
b = (j[:base] || {}).transform_keys(&:to_sym)
|
|
160
|
+
publisher = b[:publisher].to_s.empty? ? j[:publisher] : b[:publisher]
|
|
161
|
+
create(
|
|
162
|
+
type: j[:type],
|
|
163
|
+
publisher: publisher,
|
|
164
|
+
copublisher: b[:copublisher] || j[:copublisher],
|
|
165
|
+
number: (j[:number] unless j[:number].to_s.empty?),
|
|
166
|
+
year: j[:year],
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Build the base_identifier for a supplement from either an already
|
|
171
|
+
# constructed identifier or a 1.x-style attribute hash (the nested
|
|
172
|
+
# `:base` entry in a structured index). Recurses so supplement-of-
|
|
173
|
+
# supplement chains (e.g. a Corrigendum to an Amendment) build cleanly.
|
|
174
|
+
def self.build_base_identifier(base)
|
|
175
|
+
return base if base.is_a?(::Pubid::Identifier)
|
|
176
|
+
|
|
177
|
+
create(**base.transform_keys(&:to_sym))
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# When `stage:` is explicit, look up the matching TypedStage via
|
|
181
|
+
# Scheme. Otherwise default to the chosen class's "published"
|
|
182
|
+
# TypedStage (so e.g. TR renders the "/TR" prefix). Returns nil if
|
|
183
|
+
# neither is available; the renderer then omits the stage prefix.
|
|
184
|
+
def self.resolve_create_typed_stage(klass, stage)
|
|
185
|
+
if stage
|
|
186
|
+
ts = locate_create_typed_stage(stage)
|
|
187
|
+
ts && retype_stage_for_class(klass, ts)
|
|
188
|
+
elsif klass.const_defined?(:TYPED_STAGES)
|
|
189
|
+
klass.const_get(:TYPED_STAGES).find do |ts|
|
|
190
|
+
ts.stage_code.to_sym == :published
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Indexes store a supplement's stage as the bare review-stage abbr
|
|
196
|
+
# ("CD", "WD", "AWI") plus a separate type ("AMD"), so the global lookup
|
|
197
|
+
# resolves the stage to the IS-typed variant (cdis) rather than the
|
|
198
|
+
# amendment-typed one (committee_draft_amd). When the resolved stage's
|
|
199
|
+
# type differs from the class chosen via `type:`, re-pick the equivalent
|
|
200
|
+
# stage from the class's own TYPED_STAGES. harmonized_stages is the
|
|
201
|
+
# stable cross-type key (stage_code/abbr diverge between IS and amd:
|
|
202
|
+
# IS "WD" is :working_draft, the amendment is :wd_amd).
|
|
203
|
+
def self.retype_stage_for_class(klass, ts)
|
|
204
|
+
return ts unless klass.const_defined?(:TYPED_STAGES)
|
|
205
|
+
return ts unless klass.respond_to?(:type) && klass.type
|
|
206
|
+
return ts if ts.type_code.to_s == klass.type[:key].to_s
|
|
207
|
+
|
|
208
|
+
harmonized = Array(ts.harmonized_stages)
|
|
209
|
+
return ts if harmonized.empty?
|
|
210
|
+
|
|
211
|
+
klass.const_get(:TYPED_STAGES).find do |s|
|
|
212
|
+
(Array(s.harmonized_stages) & harmonized).any?
|
|
213
|
+
end || ts
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Resolve a TypedStage from a create() :stage value. The index may
|
|
217
|
+
# supply it as an abbreviation ("DIS"), a generic stage_code (:dis),
|
|
218
|
+
# or a unique per-typed-stage code (:dtr, :fdisp). Try each in turn.
|
|
219
|
+
def self.locate_create_typed_stage(stage)
|
|
220
|
+
Scheme.locate_typed_stage_by_abbr(stage.to_s) ||
|
|
221
|
+
Scheme.locate_typed_stage_by_stage_code(stage) ||
|
|
222
|
+
Scheme.locate_typed_stage_by_code(stage)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def self.resolve_create_class(type:, stage:, base: nil)
|
|
226
|
+
klass =
|
|
227
|
+
if type
|
|
228
|
+
located = locate_klass_by_type_or_short(type)
|
|
229
|
+
raise ArgumentError, "Unknown ISO type: #{type.inspect}" unless located
|
|
230
|
+
|
|
231
|
+
located
|
|
232
|
+
elsif stage
|
|
233
|
+
ts = locate_create_typed_stage(stage)
|
|
234
|
+
ts && Scheme.locate_identifier_klass_by_type_code(ts.type_code)
|
|
235
|
+
end
|
|
236
|
+
# A bare `base:` with no type/stage is still a supplement; fall back to
|
|
237
|
+
# the generic Supplement (which can hold a base) rather than
|
|
238
|
+
# InternationalStandard (which cannot).
|
|
239
|
+
klass ||= Identifiers::Supplement if base
|
|
240
|
+
klass || Identifiers::InternationalStandard
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Try direct key lookup, then a case-insensitive key lookup (indexes
|
|
244
|
+
# store e.g. "DATA" but the registry key is :data), then fall back to
|
|
245
|
+
# matching the class's :short letter (e.g. type "R" → Recommendation,
|
|
246
|
+
# whose key is :rec and short is "R"). Indexes and legacy data carry
|
|
247
|
+
# either the key, an upper-cased key, or the short form.
|
|
248
|
+
def self.locate_klass_by_type_or_short(type)
|
|
249
|
+
Scheme.locate_identifier_klass_by_type_code(type) ||
|
|
250
|
+
Scheme.locate_identifier_klass_by_type_code(type.to_s.downcase) ||
|
|
251
|
+
Scheme.identifiers.detect { |k| k.type&.dig(:short)&.to_s == type.to_s }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def self.supplement_klass?(klass)
|
|
255
|
+
Array(Scheme.instance.supplement_identifiers).include?(klass)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def self.coerce_create_attrs(opts)
|
|
259
|
+
out = {}
|
|
260
|
+
if (v = opts[:publisher])
|
|
261
|
+
# Mirror parse: the publisher carries its copublishers in its own
|
|
262
|
+
# copublisher list, and each copublisher is also a standalone entry
|
|
263
|
+
# in the copublishers collection. No copublisher → [] (parity with
|
|
264
|
+
# parsed plain-ISO identifiers).
|
|
265
|
+
cops = Array(opts[:copublisher]).map(&:to_s)
|
|
266
|
+
out[:publisher] = Components::Publisher.new(
|
|
267
|
+
publisher: v.to_s, copublisher: cops,
|
|
268
|
+
)
|
|
269
|
+
end
|
|
270
|
+
if opts[:copublisher]
|
|
271
|
+
out[:copublishers] = Array(opts[:copublisher]).map do |c|
|
|
272
|
+
Components::Publisher.new(publisher: c.to_s)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
%i[number part subpart].each do |k|
|
|
276
|
+
v = opts[k]
|
|
277
|
+
out[k] = Components::Code.new(number: v.to_s) unless v.nil?
|
|
278
|
+
end
|
|
279
|
+
if (v = opts[:year])
|
|
280
|
+
out[:date] = ::Pubid::Components::Date.new(year: v.to_s)
|
|
281
|
+
end
|
|
282
|
+
if (v = opts[:edition])
|
|
283
|
+
if v.is_a?(Hash)
|
|
284
|
+
# 1.x stored a directive org-variant ("ISO/IEC DIR 2 ISO" /
|
|
285
|
+
# "ISO/IEC DIR 1 IEC:2023") as edition: {publisher:, year:}; parse
|
|
286
|
+
# models the org as `part` and the year as `date`. Map it so create
|
|
287
|
+
# round-trips parse.
|
|
288
|
+
eh = v.transform_keys(&:to_sym)
|
|
289
|
+
out[:part] ||= Components::Code.new(number: eh[:publisher].to_s) if eh[:publisher]
|
|
290
|
+
out[:date] ||= ::Pubid::Components::Date.new(year: eh[:year].to_s) if eh[:year]
|
|
291
|
+
else
|
|
292
|
+
out[:edition] = ::Pubid::Components::Edition.new(number: v)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
if (v = opts[:language])
|
|
296
|
+
out[:languages] =
|
|
297
|
+
[::Pubid::Components::Language.new(code: v.to_s)]
|
|
298
|
+
end
|
|
299
|
+
# TcDocument committee fields: the 1.x/index shape is the flat
|
|
300
|
+
# tctype/tcnumber/… keys, but the 2.x model stores underscored Code
|
|
301
|
+
# components (mirrors the parser's builder). Without this mapping a
|
|
302
|
+
# TC document round-trips to a bare "ISO N <num>".
|
|
303
|
+
{ tctype: :tc_type, tcnumber: :tc_number,
|
|
304
|
+
sctype: :sc_type, scnumber: :sc_number,
|
|
305
|
+
wgtype: :wg_type, wgnumber: :wg_number }.each do |src, dest|
|
|
306
|
+
v = opts[src]
|
|
307
|
+
out[dest] = Components::Code.new(number: v.to_s) unless v.nil?
|
|
308
|
+
end
|
|
309
|
+
# Directives subgroup (e.g. "ISO/IEC JTC 1 DIR"): the 1.x/index shape
|
|
310
|
+
# is dirtype: "JTC", jtc_dir: "DIR", number: "1"; the 2.x model folds
|
|
311
|
+
# the subgroup into a single `subgroup` Code ("JTC 1") with no
|
|
312
|
+
# directive number, mirroring parse. Without this, .create drops the
|
|
313
|
+
# subgroup and collapses the id into a plain "DIR 1".
|
|
314
|
+
if opts[:dirtype]
|
|
315
|
+
out[:subgroup] = Components::Code.new(
|
|
316
|
+
number: [opts[:dirtype], opts[:number]].compact.join(" "),
|
|
317
|
+
)
|
|
318
|
+
out.delete(:number)
|
|
319
|
+
end
|
|
320
|
+
# TODO(create-shim): 1.x also accepted iteration, amendments,
|
|
321
|
+
# corrigendums, addendum, month, dir. Add as relaton call sites
|
|
322
|
+
# require them.
|
|
323
|
+
out
|
|
324
|
+
end
|
|
325
|
+
private_class_method :resolve_create_class, :supplement_klass?,
|
|
326
|
+
:resolve_create_typed_stage, :coerce_create_attrs,
|
|
327
|
+
:build_bundled, :build_joint_supplement
|
|
67
328
|
end
|
|
68
329
|
end
|
|
69
330
|
end
|
data/lib/pubid/iso/parser.rb
CHANGED
|
@@ -45,7 +45,9 @@ module Pubid
|
|
|
45
45
|
# ISO/JTC 1 N 456
|
|
46
46
|
# ISO/TC 184/SC 4 N 789 (TC and SC only, no WG)
|
|
47
47
|
rule(:tc_document) do
|
|
48
|
-
|
|
48
|
+
# Accept either "ISO/TC 184…" or the lenient space form "ISO TC 184…"
|
|
49
|
+
# (the latter was valid in pubid 1.x via an optional slash).
|
|
50
|
+
prefix_sole_publisher >> (str("/") | space) >> tc_type >> space >> tc_number.as(:tc_number) >>
|
|
49
51
|
tc_subcommittee_part.maybe >>
|
|
50
52
|
space >> str("N") >> space? >> digits.as(:number) >>
|
|
51
53
|
(str(":") >> year_digits.as(:year)).maybe
|
|
@@ -378,7 +380,7 @@ module Pubid
|
|
|
378
380
|
|
|
379
381
|
DIRECTIVES_TYPED_STAGES = Identifiers::Directives::TYPED_STAGES.map(&:abbr).flatten.sort_by(&:length).reverse
|
|
380
382
|
rule(:directives_identifier_no_third) do
|
|
381
|
-
prefix_with_copublishers >> space >>
|
|
383
|
+
prefix_with_copublishers >> space? >>
|
|
382
384
|
(directives_publisher_subgroup >> space).maybe >>
|
|
383
385
|
array_to_str(DIRECTIVES_TYPED_STAGES).as(:type_with_stage) >>
|
|
384
386
|
(
|
data/lib/pubid/iso/scheme.rb
CHANGED
|
@@ -36,6 +36,12 @@ module Pubid
|
|
|
36
36
|
instance.locate_typed_stage_by_stage_code(stage_code)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# @param code [String, Symbol] the per-typed-stage code to find
|
|
40
|
+
# @return [TypedStage, nil] the matching typed stage
|
|
41
|
+
def locate_typed_stage_by_code(code)
|
|
42
|
+
instance.locate_typed_stage_by_code(code)
|
|
43
|
+
end
|
|
44
|
+
|
|
39
45
|
# @param harmonized_code [String] the harmonized stage code to find
|
|
40
46
|
# @return [TypedStage, nil] the matching typed stage
|
|
41
47
|
def locate_typed_stage_by_harmonized_code(harmonized_code)
|
|
@@ -11,7 +11,8 @@ module Pubid
|
|
|
11
11
|
|
|
12
12
|
def build_rendering_context(_renderer, format:, with_edition: false,
|
|
13
13
|
lang: :en, lang_single: false,
|
|
14
|
-
stage_format_long: nil, with_date: nil
|
|
14
|
+
stage_format_long: nil, with_date: nil,
|
|
15
|
+
annotated: false)
|
|
15
16
|
if format == :mr_string
|
|
16
17
|
nil
|
|
17
18
|
elsif lang_single || stage_format_long || !with_date.nil?
|
|
@@ -19,17 +20,19 @@ module Pubid
|
|
|
19
20
|
with_language_code: lang_single ? :single : :none,
|
|
20
21
|
stage_format_long: stage_format_long || false,
|
|
21
22
|
with_date: with_date.nil? || with_date,
|
|
23
|
+
annotated: annotated,
|
|
22
24
|
)
|
|
23
25
|
else
|
|
24
|
-
detect_rendering_context
|
|
26
|
+
detect_rendering_context(annotated: annotated)
|
|
25
27
|
end
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
def detect_rendering_context
|
|
30
|
+
def detect_rendering_context(annotated: false)
|
|
29
31
|
Rendering::RenderingContext.new(
|
|
30
32
|
with_language_code: detect_language_code_format,
|
|
31
33
|
stage_format_long: detect_stage_format_long,
|
|
32
34
|
with_date: true,
|
|
35
|
+
annotated: annotated,
|
|
33
36
|
)
|
|
34
37
|
end
|
|
35
38
|
|
|
@@ -78,9 +78,10 @@ module Pubid
|
|
|
78
78
|
part_comp = part_component
|
|
79
79
|
parts << part_comp if part_comp
|
|
80
80
|
|
|
81
|
-
# Stage (only for non-published documents)
|
|
81
|
+
# Stage (only for non-published documents); an all-parts series
|
|
82
|
+
# reference carries no specific stage.
|
|
82
83
|
stage_comp = stage_component
|
|
83
|
-
parts << stage_comp if stage_comp
|
|
84
|
+
parts << stage_comp if stage_comp && !identifier.all_parts
|
|
84
85
|
|
|
85
86
|
# Year (for published documents and when edition is present)
|
|
86
87
|
year_comp = year_component
|
|
@@ -94,6 +95,9 @@ module Pubid
|
|
|
94
95
|
lang_comp = language_component
|
|
95
96
|
parts << lang_comp if lang_comp
|
|
96
97
|
|
|
98
|
+
# Series suffix for all-parts identifiers (compact, no padding)
|
|
99
|
+
parts << "ser" if identifier.all_parts
|
|
100
|
+
|
|
97
101
|
parts.join(":")
|
|
98
102
|
end
|
|
99
103
|
|
|
@@ -228,6 +232,9 @@ module Pubid
|
|
|
228
232
|
end
|
|
229
233
|
end
|
|
230
234
|
|
|
235
|
+
# Series suffix for all-parts identifiers (compact, no padding)
|
|
236
|
+
parts << "ser" if identifier.all_parts
|
|
237
|
+
|
|
231
238
|
parts.join(":")
|
|
232
239
|
end
|
|
233
240
|
|
|
@@ -326,7 +333,14 @@ module Pubid
|
|
|
326
333
|
end
|
|
327
334
|
|
|
328
335
|
# Format as stage-XX.XX
|
|
329
|
-
|
|
336
|
+
# An iterated PRF (Proof) stage (e.g. "PRF TR 17716.2") renders as the
|
|
337
|
+
# symbolic "draft" stage in the URN rather than its harmonized numeric
|
|
338
|
+
# code; a plain PRF without an iteration keeps the harmonized code.
|
|
339
|
+
stage_part = if stage_code.to_s == "prf" && identifier.stage_iteration
|
|
340
|
+
"stage-draft"
|
|
341
|
+
else
|
|
342
|
+
"stage-#{harmonized_code}"
|
|
343
|
+
end
|
|
330
344
|
|
|
331
345
|
# For base identifiers (not supplements), include iteration in stage code
|
|
332
346
|
# For supplements, iteration goes in the version part (v1.2)
|
data/lib/pubid/iso/urn_parser.rb
CHANGED
|
@@ -71,6 +71,15 @@ module Pubid
|
|
|
71
71
|
|
|
72
72
|
parts = urn.sub("urn:iso:std:", "").split(":")
|
|
73
73
|
|
|
74
|
+
# Series suffix: a trailing "ser" marks an all-parts identifier. Strip
|
|
75
|
+
# it before component parsing so it is not misread as a language or
|
|
76
|
+
# supplement token (which previously rendered as "(SER)").
|
|
77
|
+
all_parts = false
|
|
78
|
+
if parts.last == "ser"
|
|
79
|
+
parts.pop
|
|
80
|
+
all_parts = true
|
|
81
|
+
end
|
|
82
|
+
|
|
74
83
|
# Parse publisher(s) - first part
|
|
75
84
|
publishers = parse_publisher(parts.shift)
|
|
76
85
|
|
|
@@ -212,7 +221,8 @@ module Pubid
|
|
|
212
221
|
|
|
213
222
|
# Build the identifier hash
|
|
214
223
|
build_identifier(publishers, number, part, subpart, type_code, stage_code, stage_iteration,
|
|
215
|
-
harmonized_stage_code, stage_from_abbr, year, edition, languages, supplements
|
|
224
|
+
harmonized_stage_code, stage_from_abbr, year, edition, languages, supplements,
|
|
225
|
+
all_parts)
|
|
216
226
|
end
|
|
217
227
|
|
|
218
228
|
private
|
|
@@ -302,7 +312,8 @@ module Pubid
|
|
|
302
312
|
|
|
303
313
|
# Build identifier from parsed components
|
|
304
314
|
def build_identifier(publishers, number, part, subpart, type_code, stage_code, stage_iteration,
|
|
305
|
-
harmonized_stage_code, stage_from_abbr, year, edition, languages, supplements
|
|
315
|
+
harmonized_stage_code, stage_from_abbr, year, edition, languages, supplements,
|
|
316
|
+
all_parts = false)
|
|
306
317
|
# Start with base document hash
|
|
307
318
|
base_hash = {
|
|
308
319
|
publisher: publishers.first,
|
|
@@ -414,6 +425,9 @@ typed_stage
|
|
|
414
425
|
}
|
|
415
426
|
end
|
|
416
427
|
|
|
428
|
+
# all_parts belongs on the outermost identifier (after any supplement wrapping)
|
|
429
|
+
base_hash[:all_parts] = true if all_parts
|
|
430
|
+
|
|
417
431
|
# Build the final identifier
|
|
418
432
|
builder = Pubid::Iso::Builder.new(Pubid::Iso::Scheme.new)
|
|
419
433
|
builder.build(base_hash)
|
data/lib/pubid/itu/identifier.rb
CHANGED
|
@@ -17,37 +17,102 @@ module Pubid
|
|
|
17
17
|
raise "Failed to parse ITU identifier '#{identifier}': #{e.message}"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# Factory mirroring
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# *
|
|
24
|
-
# *
|
|
20
|
+
# Factory mirroring pubid 1.x's `Pubid::Itu::Identifier.create` API.
|
|
21
|
+
# Dispatch on `:type`:
|
|
22
|
+
# * nil → Recommendation (or SpecialPublication for series "OB")
|
|
23
|
+
# * :recommendation → Identifiers::Recommendation
|
|
24
|
+
# * :annex → Identifiers::Annex
|
|
25
|
+
# * :special_publication → Identifiers::SpecialPublication
|
|
26
|
+
#
|
|
27
|
+
# Component-typed kwargs are accepted as primitives and coerced:
|
|
28
|
+
# * sector: "T" → Components::Sector.new(sector: "T")
|
|
29
|
+
# * series: "X" → Components::Series.new(series: "X")
|
|
30
|
+
# * number: "509" → Components::Code.new(number: "509")
|
|
31
|
+
# * year: "2020" → Pubid::Components::Date.new(year: "2020")
|
|
32
|
+
TYPE_KEY_TO_KLASS = {
|
|
33
|
+
recommendation: "Recommendation",
|
|
34
|
+
annex: "Annex",
|
|
35
|
+
special_publication: "SpecialPublication",
|
|
36
|
+
}.freeze
|
|
37
|
+
|
|
25
38
|
def self.create(type: nil, **kwargs)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
when nil
|
|
30
|
-
if kwargs[:series].to_s == "OB"
|
|
31
|
-
create_special_publication(**kwargs)
|
|
32
|
-
else
|
|
33
|
-
raise ArgumentError,
|
|
34
|
-
"Identifier.create without :type requires series: 'OB'"
|
|
35
|
-
end
|
|
36
|
-
else
|
|
37
|
-
raise ArgumentError,
|
|
38
|
-
"Unsupported type for Identifier.create: #{type.inspect}"
|
|
39
|
+
# Backward-compat: nil type + series "OB" → SpecialPublication.
|
|
40
|
+
if type.nil? && kwargs[:series].to_s == "OB"
|
|
41
|
+
return create_special_publication(**kwargs)
|
|
39
42
|
end
|
|
43
|
+
|
|
44
|
+
klass = resolve_create_class(type)
|
|
45
|
+
klass.new(**coerce_create_attrs(kwargs, klass: klass))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.resolve_create_class(type)
|
|
49
|
+
return Identifiers::Recommendation if type.nil?
|
|
50
|
+
|
|
51
|
+
klass_name = TYPE_KEY_TO_KLASS[type.to_sym]
|
|
52
|
+
raise ArgumentError, "Unknown ITU type: #{type.inspect}" unless klass_name
|
|
53
|
+
|
|
54
|
+
Identifiers.const_get(klass_name)
|
|
40
55
|
end
|
|
41
56
|
|
|
57
|
+
# Backward-compat helper retained for the OB-series shortcut.
|
|
42
58
|
def self.create_special_publication(number:, series: "OB", date: nil,
|
|
43
|
-
language: nil)
|
|
59
|
+
language: nil)
|
|
44
60
|
Identifiers::SpecialPublication.new(
|
|
45
|
-
series:
|
|
46
|
-
code:
|
|
47
|
-
date:
|
|
61
|
+
series: Components::Series.new(series: series.to_s),
|
|
62
|
+
code: Components::Code.new(number: number.to_s),
|
|
63
|
+
date: date,
|
|
48
64
|
language: language&.to_s,
|
|
49
65
|
)
|
|
50
66
|
end
|
|
67
|
+
|
|
68
|
+
def self.coerce_create_attrs(opts, klass: nil)
|
|
69
|
+
attrs = {}
|
|
70
|
+
if (v = opts[:sector])
|
|
71
|
+
attrs[:sector] = if v.is_a?(Components::Sector)
|
|
72
|
+
v
|
|
73
|
+
else
|
|
74
|
+
Components::Sector.new(sector: v.to_s.upcase)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
if (v = opts[:series])
|
|
78
|
+
attrs[:series] = if v.is_a?(Components::Series)
|
|
79
|
+
v
|
|
80
|
+
else
|
|
81
|
+
Components::Series.new(series: v.to_s)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
if opts[:number] || opts[:code]
|
|
85
|
+
code_value = opts[:code]
|
|
86
|
+
if code_value.is_a?(Components::Code)
|
|
87
|
+
attrs[:code] = code_value
|
|
88
|
+
else
|
|
89
|
+
attrs[:code] = Components::Code.new(
|
|
90
|
+
number: (opts[:number] || code_value).to_s,
|
|
91
|
+
subseries: opts[:subseries]&.to_s,
|
|
92
|
+
parts: opts[:parts] ? Array(opts[:parts]).map(&:to_s) : nil,
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
if (v = opts[:year])
|
|
97
|
+
attrs[:date] = Pubid::Components::Date.new(year: v.to_s)
|
|
98
|
+
end
|
|
99
|
+
attrs[:date] = opts[:date] if opts[:date].is_a?(Pubid::Components::Date)
|
|
100
|
+
attrs[:language] = opts[:language].to_s if opts[:language]
|
|
101
|
+
|
|
102
|
+
# Pass through any subclass-specific kwarg the chosen class
|
|
103
|
+
# declares (e.g. Annex#base) — preserves callers that already pass
|
|
104
|
+
# an explicit attribute that our coercion table doesn't cover.
|
|
105
|
+
if klass
|
|
106
|
+
consumed = %i[sector series number code subseries parts year date
|
|
107
|
+
language]
|
|
108
|
+
opts.each do |k, v|
|
|
109
|
+
next if consumed.include?(k) || attrs.key?(k)
|
|
110
|
+
attrs[k] = v if klass.attributes.key?(k)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
attrs
|
|
114
|
+
end
|
|
115
|
+
private_class_method :resolve_create_class, :coerce_create_attrs
|
|
51
116
|
end
|
|
52
117
|
end
|
|
53
118
|
end
|