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
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "code"
|
|
4
|
+
require_relative "date"
|
|
5
|
+
require_relative "edition"
|
|
6
|
+
require_relative "language"
|
|
7
|
+
require_relative "publisher"
|
|
8
|
+
|
|
9
|
+
module Pubid
|
|
10
|
+
module Components
|
|
11
|
+
# Coerces loose primitive kwargs (matching pubid 1.x's `Identifier.create`
|
|
12
|
+
# signature) into the structured Component objects pubid 2.x expects.
|
|
13
|
+
#
|
|
14
|
+
# Used by per-flavor `.create` factories.
|
|
15
|
+
module Factory
|
|
16
|
+
# Per-kwarg coercer. Returns a Component, or an Array<Component> for
|
|
17
|
+
# collection attributes such as `languages`.
|
|
18
|
+
COERCERS = {
|
|
19
|
+
publisher: ->(v) { Publisher.new(body: v.to_s) },
|
|
20
|
+
number: ->(v) { Code.new(value: v.to_s) },
|
|
21
|
+
part: ->(v) { Code.new(value: v.to_s) },
|
|
22
|
+
subpart: ->(v) { Code.new(value: v.to_s) },
|
|
23
|
+
year: ->(v) { Date.new(year: v.to_s) },
|
|
24
|
+
edition: ->(v) { Edition.new(number: v) },
|
|
25
|
+
language: ->(v) { [Language.new(code: v.to_s)] },
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
# 1.x kwarg name → 2.x attribute name.
|
|
29
|
+
# Applied after coercion.
|
|
30
|
+
KEY_RENAMES = {
|
|
31
|
+
year: :date,
|
|
32
|
+
language: :languages,
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
# Convert a hash of 1.x-style primitive kwargs into a hash of 2.x-style
|
|
36
|
+
# Component-valued attributes ready for `<IdentifierClass>.new(...)`.
|
|
37
|
+
#
|
|
38
|
+
# Unknown keys pass through unchanged; nil values are dropped.
|
|
39
|
+
def self.from_hash(opts)
|
|
40
|
+
opts.each_with_object({}) do |(k, v), out|
|
|
41
|
+
next if v.nil?
|
|
42
|
+
|
|
43
|
+
target = KEY_RENAMES.fetch(k, k)
|
|
44
|
+
coercer = COERCERS[k]
|
|
45
|
+
out[target] = coercer ? coercer.call(v) : v
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -6,6 +6,10 @@ module Pubid
|
|
|
6
6
|
module Components
|
|
7
7
|
class TypedStage < Lutaml::Model::Serializable
|
|
8
8
|
attribute :name, :string
|
|
9
|
+
# Unique per-typed-stage code (e.g. :dtr, :fdisp). Distinct from the
|
|
10
|
+
# generic stage_code (e.g. :draft, :fdis) which is shared across types.
|
|
11
|
+
# Some index data serializes this code, so it must be resolvable.
|
|
12
|
+
attribute :code, :string
|
|
9
13
|
attribute :type_code, :string
|
|
10
14
|
attribute :stage_code, :string
|
|
11
15
|
attribute :abbr, :string, collection: true
|
data/lib/pubid/components.rb
CHANGED
|
@@ -5,6 +5,7 @@ module Pubid
|
|
|
5
5
|
autoload :Code, "pubid/components/code"
|
|
6
6
|
autoload :Date, "pubid/components/date"
|
|
7
7
|
autoload :Edition, "pubid/components/edition"
|
|
8
|
+
autoload :Factory, "pubid/components/factory"
|
|
8
9
|
autoload :Language, "pubid/components/language"
|
|
9
10
|
autoload :Locality, "pubid/components/locality"
|
|
10
11
|
autoload :Publisher, "pubid/components/publisher"
|
data/lib/pubid/csa/identifier.rb
CHANGED
|
@@ -3,6 +3,62 @@
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Csa
|
|
5
5
|
class Identifier
|
|
6
|
+
# Factory that builds a CSA identifier from a hash of primitives.
|
|
7
|
+
# Default is {Identifiers::Standard}.
|
|
8
|
+
TYPE_KEY_TO_KLASS = {
|
|
9
|
+
standard: "Standard",
|
|
10
|
+
bundled: "Bundled",
|
|
11
|
+
canadian_adopted: "CanadianAdopted",
|
|
12
|
+
cec: "Cec",
|
|
13
|
+
combined: "Combined",
|
|
14
|
+
csa_adopted: "CsaAdopted",
|
|
15
|
+
package: "Package",
|
|
16
|
+
series: "Series",
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def self.create(type: nil, **opts)
|
|
20
|
+
klass = resolve_create_class(type)
|
|
21
|
+
klass.new(**coerce_create_attrs(opts, klass: klass))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.resolve_create_class(type)
|
|
25
|
+
return Identifiers::Standard if type.nil?
|
|
26
|
+
|
|
27
|
+
klass_name = TYPE_KEY_TO_KLASS[type.to_sym]
|
|
28
|
+
raise ArgumentError, "Unknown CSA type: #{type.inspect}" unless klass_name
|
|
29
|
+
|
|
30
|
+
Identifiers.const_get(klass_name)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.coerce_create_attrs(opts, klass:)
|
|
34
|
+
attrs = {}
|
|
35
|
+
|
|
36
|
+
if (v = opts[:code] || opts[:number])
|
|
37
|
+
attrs[:code] = Pubid::Components::Code.new(value: v.to_s)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if opts[:year]
|
|
41
|
+
year_str = opts[:year].to_s
|
|
42
|
+
attrs[:year] = year_str
|
|
43
|
+
# Preserve 4-digit year rendering (e.g. "B51:2024") rather than
|
|
44
|
+
# collapsing to "B51:24".
|
|
45
|
+
attrs[:original_year_4digit] = year_str.length == 4 unless opts.key?(:original_year_4digit)
|
|
46
|
+
end
|
|
47
|
+
attrs[:original_year_4digit] = opts[:original_year_4digit] if opts.key?(:original_year_4digit)
|
|
48
|
+
attrs[:year_format] = opts[:year_format].to_s if opts[:year_format]
|
|
49
|
+
attrs[:year_prefix] = opts[:year_prefix].to_s if opts[:year_prefix]
|
|
50
|
+
attrs[:reaffirmation] = opts[:reaffirmation].to_s if opts[:reaffirmation]
|
|
51
|
+
attrs[:french] = opts[:french] if opts.key?(:french)
|
|
52
|
+
attrs[:has_publisher] = opts.fetch(:has_publisher, true)
|
|
53
|
+
attrs[:publisher_prefix] = opts[:publisher_prefix].to_s if opts[:publisher_prefix]
|
|
54
|
+
attrs[:series_prefix] = opts[:series_prefix].to_s if opts[:series_prefix]
|
|
55
|
+
attrs[:series] = opts[:series] if opts.key?(:series) && [true, false].include?(opts[:series])
|
|
56
|
+
attrs[:package] = opts[:package].to_s if opts[:package]
|
|
57
|
+
attrs[:no_number] = opts[:no_number].to_s if opts[:no_number]
|
|
58
|
+
attrs
|
|
59
|
+
end
|
|
60
|
+
private_class_method :resolve_create_class, :coerce_create_attrs
|
|
61
|
+
|
|
6
62
|
def self.parse(input)
|
|
7
63
|
# Filter out comments
|
|
8
64
|
return nil if input.start_with?("#")
|
|
@@ -9,6 +9,49 @@ module Pubid
|
|
|
9
9
|
rescue Parslet::ParseFailed => e
|
|
10
10
|
raise "Failed to parse ETSI identifier '#{identifier}': #{e.message}"
|
|
11
11
|
end
|
|
12
|
+
|
|
13
|
+
# Factory mirroring pubid 1.x's `Pubid::Etsi::Identifier.create` API.
|
|
14
|
+
#
|
|
15
|
+
# ETSI's `type` kwarg (EN, ES, EG, TS, TR, GS, GR, GTS, …) is data
|
|
16
|
+
# stored on the identifier instance, not a class-dispatch key — all
|
|
17
|
+
# ETSI standards share the {Identifiers::EtsiStandard} class.
|
|
18
|
+
#
|
|
19
|
+
# @param opts [Hash] :type, :code/:number, :parts, :version, :year,
|
|
20
|
+
# :month, :date
|
|
21
|
+
def self.create(**opts)
|
|
22
|
+
Identifiers::EtsiStandard.new(**coerce_create_attrs(opts))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.coerce_create_attrs(opts)
|
|
26
|
+
attrs = {}
|
|
27
|
+
attrs[:type] = opts[:type].to_s if opts[:type]
|
|
28
|
+
|
|
29
|
+
code_value = opts[:code] || opts[:number]
|
|
30
|
+
if code_value
|
|
31
|
+
attrs[:code] = Pubid::Etsi::Components::Code.new(
|
|
32
|
+
number: code_value.to_s,
|
|
33
|
+
parts: opts[:parts] ? Array(opts[:parts]).map(&:to_s) : nil,
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if (v = opts[:version])
|
|
38
|
+
attrs[:version] = Pubid::Etsi::Components::Version.new(
|
|
39
|
+
version: v.to_s,
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if opts[:year] || opts[:month]
|
|
44
|
+
attrs[:date] = ::Pubid::Components::Date.new(
|
|
45
|
+
year: opts[:year]&.to_s,
|
|
46
|
+
month: opts[:month]&.to_s,
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# TODO(create-shim): Amendment and Corrigendum supplements need a
|
|
51
|
+
# base_identifier and aren't yet wired through.
|
|
52
|
+
attrs
|
|
53
|
+
end
|
|
54
|
+
private_class_method :coerce_create_attrs
|
|
12
55
|
end
|
|
13
56
|
end
|
|
14
57
|
end
|
data/lib/pubid/identifier.rb
CHANGED
|
@@ -39,6 +39,11 @@ module Pubid
|
|
|
39
39
|
nil
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# @return [String, nil] publication year from the date component
|
|
43
|
+
def year
|
|
44
|
+
date&.year&.to_s
|
|
45
|
+
end
|
|
46
|
+
|
|
42
47
|
def initialize(attrs = {}, options = {})
|
|
43
48
|
attrs = attrs.dup
|
|
44
49
|
attrs[:_type] ||= self.class.polymorphic_name
|
|
@@ -207,7 +212,8 @@ module Pubid
|
|
|
207
212
|
|
|
208
213
|
def build_rendering_context(_renderer, format:, with_edition: false,
|
|
209
214
|
lang: :en, lang_single: false,
|
|
210
|
-
stage_format_long: nil, with_date: nil
|
|
215
|
+
stage_format_long: nil, with_date: nil,
|
|
216
|
+
annotated: false)
|
|
211
217
|
if format == :mr_string
|
|
212
218
|
nil
|
|
213
219
|
else
|
|
@@ -215,6 +221,7 @@ module Pubid
|
|
|
215
221
|
with_language_code: lang_single ? :single : :none,
|
|
216
222
|
stage_format_long: stage_format_long || false,
|
|
217
223
|
with_date: with_date.nil? || with_date,
|
|
224
|
+
annotated: annotated,
|
|
218
225
|
)
|
|
219
226
|
end
|
|
220
227
|
end
|
data/lib/pubid/idf/identifier.rb
CHANGED
|
@@ -64,6 +64,66 @@ module Pubid
|
|
|
64
64
|
|
|
65
65
|
Pubid::Idf::Builder.new.build(parsed)
|
|
66
66
|
end
|
|
67
|
+
|
|
68
|
+
# Factory mirroring pubid 1.x's `Pubid::Idf::Identifier.create` API.
|
|
69
|
+
# Default subclass is {Identifiers::InternationalStandard}.
|
|
70
|
+
#
|
|
71
|
+
# IDF's renderer requires `typed_stage` to be set (calls
|
|
72
|
+
# `.abbreviation` without a nil check); factory auto-resolves the
|
|
73
|
+
# "published" TypedStage for the chosen subclass.
|
|
74
|
+
TYPE_KEY_TO_KLASS = {
|
|
75
|
+
is: "InternationalStandard",
|
|
76
|
+
reviewed_method: "ReviewedMethod",
|
|
77
|
+
}.freeze
|
|
78
|
+
|
|
79
|
+
def self.create(type: nil, stage: nil, **opts)
|
|
80
|
+
klass = resolve_create_class(type)
|
|
81
|
+
attrs = coerce_create_attrs(opts)
|
|
82
|
+
ts = resolve_create_typed_stage(klass, stage)
|
|
83
|
+
attrs[:typed_stage] = ts if ts
|
|
84
|
+
klass.new(**attrs)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.resolve_create_class(type)
|
|
88
|
+
return Identifiers::InternationalStandard if type.nil?
|
|
89
|
+
|
|
90
|
+
klass_name = TYPE_KEY_TO_KLASS[type.to_sym]
|
|
91
|
+
raise ArgumentError, "Unknown IDF type: #{type.inspect}" unless klass_name
|
|
92
|
+
|
|
93
|
+
Identifiers.const_get(klass_name)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.resolve_create_typed_stage(klass, stage)
|
|
97
|
+
return nil unless klass.const_defined?(:TYPED_STAGES)
|
|
98
|
+
|
|
99
|
+
if stage
|
|
100
|
+
klass.const_get(:TYPED_STAGES).find do |ts|
|
|
101
|
+
ts.abbr.include?(stage.to_s)
|
|
102
|
+
end
|
|
103
|
+
else
|
|
104
|
+
klass.const_get(:TYPED_STAGES).find do |ts|
|
|
105
|
+
ts.stage_code&.to_sym == :published
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.coerce_create_attrs(opts)
|
|
111
|
+
attrs = {
|
|
112
|
+
publisher: Pubid::Components::Publisher.new(
|
|
113
|
+
body: (opts[:publisher] || "IDF").to_s,
|
|
114
|
+
),
|
|
115
|
+
}
|
|
116
|
+
if (v = opts[:number])
|
|
117
|
+
attrs[:number] = Pubid::Components::Code.new(value: v.to_s)
|
|
118
|
+
end
|
|
119
|
+
if (v = opts[:year])
|
|
120
|
+
attrs[:date] = Pubid::Components::Date.new(year: v.to_s)
|
|
121
|
+
end
|
|
122
|
+
attrs
|
|
123
|
+
end
|
|
124
|
+
private_class_method :resolve_create_class,
|
|
125
|
+
:resolve_create_typed_stage,
|
|
126
|
+
:coerce_create_attrs
|
|
67
127
|
end
|
|
68
128
|
end
|
|
69
129
|
end
|
data/lib/pubid/iec/builder.rb
CHANGED
|
@@ -434,7 +434,8 @@ edition_data = nil, typed_stage = nil)
|
|
|
434
434
|
parse_languages(value)
|
|
435
435
|
|
|
436
436
|
when :all_parts
|
|
437
|
-
|
|
437
|
+
# Set all_parts boolean attribute directly on identifier (matches ISO builder)
|
|
438
|
+
true
|
|
438
439
|
|
|
439
440
|
when :database
|
|
440
441
|
# Database flag - return true if DB suffix present
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
require "lutaml/model"
|
|
2
|
+
require_relative "../../components/code"
|
|
2
3
|
# frozen_string_literal: true
|
|
3
4
|
|
|
4
5
|
module Pubid
|
|
5
6
|
module Iec
|
|
6
7
|
module Components
|
|
7
|
-
class Code <
|
|
8
|
+
class Code < ::Pubid::Components::Code
|
|
8
9
|
attribute :prefix, :string, default: -> {}
|
|
9
10
|
attribute :number, :string
|
|
10
11
|
attribute :part, :string, default: -> {}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
require "lutaml/model"
|
|
2
|
+
require_relative "../../components/publisher"
|
|
2
3
|
# frozen_string_literal: true
|
|
3
4
|
|
|
4
5
|
module Pubid
|
|
5
6
|
module Iec
|
|
6
7
|
module Components
|
|
7
|
-
class Publisher <
|
|
8
|
+
class Publisher < ::Pubid::Components::Publisher
|
|
8
9
|
PUBLISHERS = {
|
|
9
10
|
"IEC" => "International Electrotechnical Commission",
|
|
10
11
|
"ISO/IEC" => "ISO/IEC Joint Technical Committee",
|
data/lib/pubid/iec/identifier.rb
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
require_relative "../identifier"
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
require_relative "../components/typed_stage"
|
|
4
|
+
require_relative "../components/factory"
|
|
4
5
|
|
|
5
6
|
module Pubid
|
|
6
7
|
module Iec
|
|
7
8
|
class Identifier < ::Pubid::Identifier
|
|
9
|
+
# Long-tail document types that `create` may build but which are not in
|
|
10
|
+
# Scheme.identifiers (the parse/build candidate set). Loaded lazily (not
|
|
11
|
+
# at require time) to avoid a circular load with identifiers/base.rb.
|
|
12
|
+
EXTRA_CREATE_KLASS_FILES = %w[
|
|
13
|
+
conformity_assessment technology_report white_paper
|
|
14
|
+
societal_technology_trend_report systems_reference_document
|
|
15
|
+
interpretation_sheet
|
|
16
|
+
].freeze
|
|
8
17
|
def self.parse(string)
|
|
18
|
+
# Route URN strings to the URN parser (mirrors Iso::Identifier.parse)
|
|
19
|
+
if Pubid::FormatDetector.detect(string) == :urn
|
|
20
|
+
return Pubid::Iec::UrnParser.parse(string)
|
|
21
|
+
end
|
|
22
|
+
|
|
9
23
|
# Apply legacy update_codes normalization first, before any other preprocessing
|
|
10
24
|
normalized = Core::UpdateCodes.apply(string, :iec)
|
|
11
25
|
parsed = Pubid::Iec::Parser.new.parse(normalized)
|
|
@@ -16,6 +30,227 @@ module Pubid
|
|
|
16
30
|
|
|
17
31
|
Pubid::Iec::Builder.new(Pubid::Iec::Scheme).build(parsed)
|
|
18
32
|
end
|
|
33
|
+
|
|
34
|
+
# Factory mirroring pubid 1.x's `Pubid::Iec::Identifier.create` API.
|
|
35
|
+
# See {Pubid::Iso::Identifier.create} for the shared design. Builds the
|
|
36
|
+
# IEC `Components::*` subclasses (not the base ones) and populates
|
|
37
|
+
# type/stage from the resolved TypedStage, so that a created identifier
|
|
38
|
+
# round-trips `==` against the same identifier produced by `parse`.
|
|
39
|
+
def self.create(type: nil, stage: nil, **opts)
|
|
40
|
+
# A VAP suffix (CSV/RLV/…) is the outermost wrapper around the base
|
|
41
|
+
# document (which may itself be amended); rebuild it first.
|
|
42
|
+
if (vap = opts.delete(:vap))
|
|
43
|
+
base = create(type: type, stage: stage, **opts)
|
|
44
|
+
return Identifiers::VapIdentifier.new(
|
|
45
|
+
base_identifier: base,
|
|
46
|
+
vap_suffix: Components::VapSuffix.new(code: Array(vap).first.to_s),
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Structured index rows carry amendments/corrigendums as a flat list
|
|
51
|
+
# alongside the base document's keys; rebuild the supplement wrapping
|
|
52
|
+
# the recursively-created base, mirroring what parse produces.
|
|
53
|
+
if (supp = extract_supplement(opts))
|
|
54
|
+
return build_supplement(supp, type: type, stage: stage, opts: opts)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# A nested base: holds the base document of a supplement whose own
|
|
58
|
+
# number/year sit at the top level (e.g. an Interpretation Sheet).
|
|
59
|
+
if (base_hash = opts.delete(:base))
|
|
60
|
+
return build_based_supplement(type: type, base_hash: base_hash,
|
|
61
|
+
opts: opts)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
klass = resolve_create_class(type: type, stage: stage)
|
|
65
|
+
attrs = coerce_create_attrs(opts)
|
|
66
|
+
ts = resolve_create_typed_stage(klass, stage)
|
|
67
|
+
if ts
|
|
68
|
+
attrs[:typed_stage] = ts
|
|
69
|
+
# Parse derives `type` and `stage` from the TypedStage (see
|
|
70
|
+
# Builder#cast for :type_with_stage); mirror that here.
|
|
71
|
+
attrs[:type] ||= ts.to_type
|
|
72
|
+
attrs[:stage] ||= ts.to_stage
|
|
73
|
+
end
|
|
74
|
+
klass.new(**attrs)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Pop a supplement spec off the flat opts hash, if present. Returns
|
|
78
|
+
# { klass:, entry: } or nil. Amendments take precedence over
|
|
79
|
+
# corrigendums for the (rare) consolidated rows that carry both.
|
|
80
|
+
def self.extract_supplement(opts)
|
|
81
|
+
if (amds = opts.delete(:amendments))
|
|
82
|
+
{ klass: Identifiers::Amendment, entry: Array(amds).first }
|
|
83
|
+
elsif (cors = opts.delete(:corrigendums))
|
|
84
|
+
{ klass: Identifiers::Corrigendum, entry: Array(cors).first }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Build an Amendment/Corrigendum from { number:, year: } wrapping a base
|
|
89
|
+
# identifier created from the remaining opts (which may themselves carry
|
|
90
|
+
# a type, e.g. an amendment to a TR).
|
|
91
|
+
def self.build_supplement(supp, type:, stage:, opts:)
|
|
92
|
+
base = create(type: type, stage: stage, **opts)
|
|
93
|
+
klass = supp[:klass]
|
|
94
|
+
entry = supp[:entry] || {}
|
|
95
|
+
ts = klass::TYPED_STAGES.find { |t| t.stage_code.to_sym == :published }
|
|
96
|
+
attrs = { base_identifier: base, typed_stage: ts,
|
|
97
|
+
type: ts.to_type, stage: ts.to_stage }
|
|
98
|
+
if (n = entry[:number])
|
|
99
|
+
attrs[:number] = Components::Code.new(number: n.to_s)
|
|
100
|
+
end
|
|
101
|
+
if (y = entry[:year])
|
|
102
|
+
attrs[:date] = ::Pubid::Components::Date.new(year: y.to_s)
|
|
103
|
+
end
|
|
104
|
+
klass.new(**attrs)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Build a supplement whose base document is carried in a nested base:
|
|
108
|
+
# hash (e.g. type: "ISH"); the supplement's own number/year are the
|
|
109
|
+
# remaining top-level keys.
|
|
110
|
+
def self.build_based_supplement(type:, base_hash:, opts:)
|
|
111
|
+
klass = (type && locate_klass_by_type_or_short(type)) ||
|
|
112
|
+
Identifiers::InternationalStandard
|
|
113
|
+
base = create(**base_hash.transform_keys(&:to_sym))
|
|
114
|
+
attrs = coerce_create_attrs(opts)
|
|
115
|
+
.slice(:number, :part, :subpart, :date, :publisher, :copublishers)
|
|
116
|
+
attrs[:base_identifier] = base
|
|
117
|
+
if klass.const_defined?(:TYPED_STAGES) &&
|
|
118
|
+
(ts = klass::TYPED_STAGES.find { |t| t.stage_code.to_sym == :published })
|
|
119
|
+
attrs[:typed_stage] = ts
|
|
120
|
+
attrs[:type] ||= ts.to_type
|
|
121
|
+
attrs[:stage] ||= ts.to_stage
|
|
122
|
+
end
|
|
123
|
+
klass.new(**attrs)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Coerce a 1.x-style attribute hash into IEC Component instances,
|
|
127
|
+
# matching what the parser/builder produces. Unknown keys are dropped.
|
|
128
|
+
def self.coerce_create_attrs(opts)
|
|
129
|
+
out = {}
|
|
130
|
+
if (v = opts[:publisher])
|
|
131
|
+
out[:publisher] = Components::Publisher.new(body: v.to_s)
|
|
132
|
+
end
|
|
133
|
+
if (copubs = opts[:copublishers] || opts[:copublisher])
|
|
134
|
+
out[:copublishers] =
|
|
135
|
+
Array(copubs).map { |c| Components::Publisher.new(body: c.to_s) }
|
|
136
|
+
end
|
|
137
|
+
if (v = opts[:number])
|
|
138
|
+
out[:number] = Components::Code.new(number: v.to_s)
|
|
139
|
+
end
|
|
140
|
+
# Indexes fold the subpart into a single "2-4" part string; parse
|
|
141
|
+
# keeps part and subpart separate, so split to match.
|
|
142
|
+
part, subpart = split_part(opts[:part], opts[:subpart])
|
|
143
|
+
out[:part] = Components::Code.new(number: part.to_s) unless part.nil?
|
|
144
|
+
out[:subpart] = Components::Code.new(number: subpart.to_s) unless subpart.nil?
|
|
145
|
+
if (v = opts[:year])
|
|
146
|
+
out[:date] = ::Pubid::Components::Date.new(year: v.to_s)
|
|
147
|
+
end
|
|
148
|
+
if (v = opts[:edition])
|
|
149
|
+
out[:edition] = ::Pubid::Components::Edition.new(number: v)
|
|
150
|
+
end
|
|
151
|
+
if (v = opts[:language])
|
|
152
|
+
out[:languages] = [::Pubid::Components::Language.new(code: v.to_s)]
|
|
153
|
+
end
|
|
154
|
+
out[:database] = true if opts[:database]
|
|
155
|
+
out
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Split a folded "2-4" part into [part, subpart], matching parse. A part
|
|
159
|
+
# without a dash (or an explicit subpart already supplied) is untouched.
|
|
160
|
+
def self.split_part(part, subpart)
|
|
161
|
+
return [nil, subpart] if part.nil?
|
|
162
|
+
if subpart.nil? && part.to_s.include?("-")
|
|
163
|
+
part.to_s.split("-", 2)
|
|
164
|
+
else
|
|
165
|
+
[part, subpart]
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def self.resolve_create_class(type:, stage:)
|
|
170
|
+
if type && supplement_type?(type)
|
|
171
|
+
# Supplements are built from amendments:/corrigendums: data (which
|
|
172
|
+
# carry the supplement number/year); an explicit supplement `type:`
|
|
173
|
+
# alone has no base to wrap.
|
|
174
|
+
raise ArgumentError,
|
|
175
|
+
"#{type} requires a base_identifier; pass amendments:/" \
|
|
176
|
+
"corrigendums: instead of type:"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
klass =
|
|
180
|
+
if type
|
|
181
|
+
locate_klass_by_type_or_short(type)
|
|
182
|
+
elsif stage
|
|
183
|
+
ts = safe_locate_typed_stage(stage)
|
|
184
|
+
ts && locate_klass_by_type_or_short(ts.type_code)
|
|
185
|
+
end
|
|
186
|
+
klass || Identifiers::InternationalStandard
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# True when `type` names a supplement identifier (Amendment/Corrigendum/
|
|
190
|
+
# Fragment) by key, downcased key, or short abbreviation.
|
|
191
|
+
def self.supplement_type?(type)
|
|
192
|
+
t = type.to_s
|
|
193
|
+
Scheme.supplement_identifiers.any? do |k|
|
|
194
|
+
k.type[:key].to_s == t || k.type[:key].to_s == t.downcase ||
|
|
195
|
+
Array(k.type[:short]).map(&:to_s).include?(t)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Structured indexes store `type:` as the registry key (:tr), an
|
|
200
|
+
# upper-cased abbreviation ("TR"), or a title ("Technology Report").
|
|
201
|
+
# Try each spelling, across every IEC identifier class (the create
|
|
202
|
+
# candidate set is wider than Scheme.identifiers).
|
|
203
|
+
def self.locate_klass_by_type_or_short(type)
|
|
204
|
+
t = type.to_s
|
|
205
|
+
all_create_klasses.detect { |k| k.type[:key].to_s == t } ||
|
|
206
|
+
all_create_klasses.detect { |k| k.type[:key].to_s == t.downcase } ||
|
|
207
|
+
all_create_klasses.detect { |k| Array(k.type[:short]).map(&:to_s).include?(t) } ||
|
|
208
|
+
all_create_klasses.detect { |k| k.type[:title].to_s == t }
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# All IEC identifier classes that `create` may build, including the
|
|
212
|
+
# long-tail document types absent from Scheme.identifiers. Memoized;
|
|
213
|
+
# the extra classes are required here (not at load time) to dodge the
|
|
214
|
+
# circular require with identifiers/base.rb.
|
|
215
|
+
def self.all_create_klasses
|
|
216
|
+
@all_create_klasses ||= begin
|
|
217
|
+
extra = EXTRA_CREATE_KLASS_FILES.map do |f|
|
|
218
|
+
require_relative "identifiers/#{f}"
|
|
219
|
+
Identifiers.const_get(camelize_klass_file(f))
|
|
220
|
+
end
|
|
221
|
+
Scheme.identifiers + extra
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def self.camelize_klass_file(file)
|
|
226
|
+
file.split("_").map(&:capitalize).join
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# IEC Scheme raises ArgumentError on miss instead of returning nil;
|
|
230
|
+
# wrap so the same control flow works as in the ISO factory.
|
|
231
|
+
def self.safe_locate_typed_stage(abbr)
|
|
232
|
+
Scheme.locate_typed_stage_by_abbr(abbr.to_s)
|
|
233
|
+
rescue ArgumentError
|
|
234
|
+
nil
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def self.resolve_create_typed_stage(klass, stage)
|
|
238
|
+
if stage
|
|
239
|
+
safe_locate_typed_stage(stage)
|
|
240
|
+
elsif klass.const_defined?(:TYPED_STAGES)
|
|
241
|
+
klass.const_get(:TYPED_STAGES).find do |ts|
|
|
242
|
+
ts.stage_code.to_sym == :published
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
private_class_method :resolve_create_class,
|
|
247
|
+
:safe_locate_typed_stage,
|
|
248
|
+
:resolve_create_typed_stage, :coerce_create_attrs,
|
|
249
|
+
:extract_supplement, :build_supplement,
|
|
250
|
+
:build_based_supplement,
|
|
251
|
+
:locate_klass_by_type_or_short, :all_create_klasses,
|
|
252
|
+
:camelize_klass_file, :supplement_type?,
|
|
253
|
+
:split_part
|
|
19
254
|
end
|
|
20
255
|
end
|
|
21
256
|
end
|
|
@@ -38,6 +38,10 @@ module Pubid
|
|
|
38
38
|
# Database flag
|
|
39
39
|
parts << " DB" if database
|
|
40
40
|
|
|
41
|
+
# All-parts marker — rendered the same as the generic
|
|
42
|
+
# HumanReadable renderer for parity (see Pubid::Renderers::HumanReadable#render).
|
|
43
|
+
parts << " (all parts)" if all_parts
|
|
44
|
+
|
|
41
45
|
parts.compact.join
|
|
42
46
|
end
|
|
43
47
|
|
data/lib/pubid/iec/parser.rb
CHANGED
|
@@ -136,8 +136,13 @@ module Pubid
|
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
rule(:number) do
|
|
139
|
-
#
|
|
140
|
-
|
|
139
|
+
# ISO/IEC Directives: "DIR", "DIR 1", "DIR 1 IEC SUP", "DIR 2 IEC", "DIR IEC SUP"
|
|
140
|
+
# Captured as a flat number (no part/subpart) so it round-trips to the input
|
|
141
|
+
# and class-matches the IEC index. Must come before the IECEE [A-Z]{2,4} branch,
|
|
142
|
+
# which would otherwise match "DIR" alone and leave the trailing tokens unconsumed.
|
|
143
|
+
(str("DIR") >> (space >> match('[A-Z\d]').repeat(1)).repeat) |
|
|
144
|
+
# Special case for VIM publication
|
|
145
|
+
str("VIM") |
|
|
141
146
|
# Special case for SYMBOL publication
|
|
142
147
|
str("SYMBOL") |
|
|
143
148
|
# IECEx TRF version notation: 62784v1a_ds, 62784v1A
|