pubid 2.0.26 → 2.0.28
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/README.adoc +115 -167
- data/data/parg/cen_pfs.json +1 -1
- data/lib/pubid/cen_pfs/adopted_base.rb +114 -0
- data/lib/pubid/cen_pfs/builder.rb +157 -21
- data/lib/pubid/cen_pfs/identifier.rb +106 -48
- data/lib/pubid/cen_pfs/identifiers/adopted_european_norm.rb +55 -0
- data/lib/pubid/cen_pfs/identifiers/amendment.rb +27 -0
- data/lib/pubid/cen_pfs/identifiers/cen_report.rb +36 -0
- data/lib/pubid/cen_pfs/identifiers/cen_workshop_agreement.rb +36 -0
- data/lib/pubid/cen_pfs/identifiers/consolidated_identifier.rb +41 -0
- data/lib/pubid/cen_pfs/identifiers/corrigendum.rb +27 -0
- data/lib/pubid/cen_pfs/identifiers/european_norm.rb +43 -23
- data/lib/pubid/cen_pfs/identifiers/european_prestandard.rb +55 -0
- data/lib/pubid/cen_pfs/identifiers/european_specification.rb +36 -0
- data/lib/pubid/cen_pfs/identifiers/fragment.rb +28 -0
- data/lib/pubid/cen_pfs/identifiers/guide.rb +36 -0
- data/lib/pubid/cen_pfs/identifiers/harmonization_document.rb +36 -0
- data/lib/pubid/cen_pfs/identifiers/supplement.rb +39 -0
- data/lib/pubid/cen_pfs/identifiers/technical_report.rb +36 -0
- data/lib/pubid/cen_pfs/identifiers/technical_specification.rb +36 -0
- data/lib/pubid/cen_pfs/identifiers.rb +15 -1
- data/lib/pubid/cen_pfs/pubid_urn_schema.rb +20 -1
- data/lib/pubid/cen_pfs/renderer.rb +50 -1
- data/lib/pubid/cen_pfs/urn_generator.rb +75 -3
- data/lib/pubid/cen_pfs.rb +7 -16
- data/lib/pubid/version.rb +1 -1
- metadata +19 -7
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pubid
|
|
4
|
+
module CenPfs
|
|
5
|
+
# The adopted-base dispatch hook (pubid-grammar#121 feature 4, the §4
|
|
6
|
+
# coded escape hatch). The generated builder calls this module at two
|
|
7
|
+
# explicit points — the captured adopted run, and the implicit probe —
|
|
8
|
+
# and this module owns the value-dependent dispatch the declaration
|
|
9
|
+
# cannot carry:
|
|
10
|
+
#
|
|
11
|
+
# * the adopted string parses with the owning scheme's flavor
|
|
12
|
+
# ("ISO/IEC" and "ISO" leads through Pubid::Iso, "IEC" through
|
|
13
|
+
# Pubid::Iec);
|
|
14
|
+
# * an ENV-prefixed adoption switches the class to the prestandard
|
|
15
|
+
# (the coded ENV branch), the publisher staying ENV;
|
|
16
|
+
# * an EN with a bare number in the IEC range (60000-79999) adopts
|
|
17
|
+
# the corresponding IEC standard implicitly — the ISO-only range
|
|
18
|
+
# is intentionally NOT mapped (too many real CEN publications
|
|
19
|
+
# would change type; pubid#249).
|
|
20
|
+
module AdoptedBase
|
|
21
|
+
IMPLICIT_IEC_RANGE = (60_000..79_999)
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
# The adoption identifier the captured run builds, or nil when the
|
|
25
|
+
# run names no known flavor (the caller surfaces the parse error).
|
|
26
|
+
def build(builder, data)
|
|
27
|
+
adopted_str = builder.unwrap(data[:adopted])&.to_s&.strip
|
|
28
|
+
return nil unless adopted_str && !adopted_str.empty?
|
|
29
|
+
|
|
30
|
+
adopted_id = parse_adopted(adopted_str)
|
|
31
|
+
publisher = builder.unwrap(data[:publisher])&.to_s
|
|
32
|
+
return env_adopted(adopted_id) if publisher == "ENV"
|
|
33
|
+
|
|
34
|
+
attrs = adoption_attrs(builder, data, adopted_id, publisher)
|
|
35
|
+
Identifiers::AdoptedEuropeanNorm.new(**attrs)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The implicit adoption probe: an EN with a bare IEC-range number,
|
|
39
|
+
# no parts and no supplements, adopts the corresponding IEC
|
|
40
|
+
# standard (the coded builder's own guard). nil otherwise.
|
|
41
|
+
def implicit(builder, data)
|
|
42
|
+
return nil unless implicit_adoption?(builder, data)
|
|
43
|
+
|
|
44
|
+
number = builder.unwrap(data[:number])&.to_s&.strip
|
|
45
|
+
return nil unless number&.match?(/\A\d{1,6}\z/)
|
|
46
|
+
return nil unless IMPLICIT_IEC_RANGE.cover?(number.to_i)
|
|
47
|
+
|
|
48
|
+
Identifiers::AdoptedEuropeanNorm.new(
|
|
49
|
+
adopted: Pubid::Iec.parse("IEC #{number}"),
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# The ENV adoption switch: the prestandard wraps the adopted
|
|
56
|
+
# document, its publisher staying ENV.
|
|
57
|
+
def env_adopted(adopted_id)
|
|
58
|
+
Identifiers::EuropeanPrestandard.new(
|
|
59
|
+
publisher: ::Pubid::Components::Publisher.new(body: "ENV"),
|
|
60
|
+
adopted: adopted_id,
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The adoption's own columns: the draft stage rides the CEN
|
|
65
|
+
# adoption ("prEN ISO 1234:2020"), the publisher face, and the
|
|
66
|
+
# copublishers.
|
|
67
|
+
def adoption_attrs(builder, data, adopted_id, publisher)
|
|
68
|
+
attrs = { adopted: adopted_id }
|
|
69
|
+
attrs.merge!(stage_attrs(builder, data))
|
|
70
|
+
attrs[:publisher] =
|
|
71
|
+
::Pubid::Components::Publisher.new(body: publisher) if publisher
|
|
72
|
+
copubs = Array(data[:copublishers]).map do |row|
|
|
73
|
+
value = builder.unwrap(row[:copublisher] || row["copublisher"])
|
|
74
|
+
::Pubid::Components::Publisher.new(body: value.to_s)
|
|
75
|
+
end
|
|
76
|
+
attrs[:copublishers] = copubs unless copubs.empty?
|
|
77
|
+
attrs
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def stage_attrs(builder, data)
|
|
81
|
+
stage = builder.unwrap(data[:stage])
|
|
82
|
+
return {} unless stage
|
|
83
|
+
|
|
84
|
+
typed_stage = ::Pubid::CenPfs::Builder.stage_by_code(
|
|
85
|
+
::Pubid::CenPfs::Builder::STAGE_MAP.fetch(stage.to_s),
|
|
86
|
+
)
|
|
87
|
+
{ typed_stage: typed_stage, type: typed_stage.to_type,
|
|
88
|
+
stage: typed_stage.to_stage }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The coded guard: the EN publisher alone, no copublishers, no
|
|
92
|
+
# parts, no supplements — only then may the number imply adoption.
|
|
93
|
+
def implicit_adoption?(builder, data)
|
|
94
|
+
builder.unwrap(data[:publisher])&.to_s == "EN" &&
|
|
95
|
+
Array(data[:copublishers]).none? &&
|
|
96
|
+
builder.unwrap(data[:part]).nil? &&
|
|
97
|
+
Array(data[:supplements]).none?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# "EN ISO 13485:2016/Amd 1:2016" parses the ADOPTED string with the
|
|
101
|
+
# ISO flavor (its amendment spelling included); an IEC lead token
|
|
102
|
+
# parses with the IEC flavor. CISPR stays unparsed (the coded
|
|
103
|
+
# builder's own boundary).
|
|
104
|
+
def parse_adopted(adopted_str)
|
|
105
|
+
if adopted_str.start_with?("IEC")
|
|
106
|
+
Pubid::Iec.parse(adopted_str)
|
|
107
|
+
elsif adopted_str.start_with?("ISO")
|
|
108
|
+
Pubid::Iso.parse(adopted_str)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -1,33 +1,169 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
module Pubid
|
|
2
|
+
module CenPfs
|
|
3
|
+
class Builder
|
|
4
|
+
# The type each declared token selects (feature 1); the
|
|
5
|
+
# token-free type is the default class.
|
|
6
|
+
TYPE_TOKENS = {
|
|
7
|
+
"TR" => "TechnicalReport",
|
|
8
|
+
"TS" => "TechnicalSpecification",
|
|
9
|
+
"Guide" => "Guide",
|
|
10
|
+
"CR" => "CenReport",
|
|
11
|
+
"CWA" => "CenWorkshopAgreement",
|
|
12
|
+
"ES" => "EuropeanSpecification",
|
|
13
|
+
"ENV" => "EuropeanPrestandard",
|
|
14
|
+
"HD" => "HarmonizationDocument",
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
DEFAULT_TYPE = "EuropeanNorm"
|
|
18
|
+
|
|
19
|
+
# The declared draft stages (feature 2): code → the
|
|
20
|
+
# typed-stage row the trio builds from.
|
|
21
|
+
STAGES = {
|
|
22
|
+
"pren" => { code: :pren, stage_code: :proposal, type_code: :en, abbr: ["prEN"] },
|
|
23
|
+
"fpren" => { code: :fpren, stage_code: :final_proposal, type_code: :en, abbr: ["FprEN"] },
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
# The captured spelling → the serialized stage code.
|
|
27
|
+
STAGE_MAP = {
|
|
28
|
+
"prEN" => "pren",
|
|
29
|
+
"FprEN" => "fpren",
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
def self.build(parsed_data)
|
|
33
|
+
new.build(parsed_data)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.stage_by_code(code)
|
|
37
|
+
row = STAGES[code.to_s]
|
|
38
|
+
return nil unless row
|
|
39
|
+
|
|
40
|
+
::Pubid::Components::TypedStage.new(
|
|
41
|
+
code: row[:code], stage_code: row[:stage_code],
|
|
42
|
+
type_code: row[:type_code], abbr: row[:abbr],
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def build(data)
|
|
47
|
+
base = build_base_document(data)
|
|
48
|
+
wrapped = wrap_supplements(base, data)
|
|
49
|
+
wrap_fragment(wrapped, data)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The document the supplements apply to. The adopted-base
|
|
53
|
+
# dispatch (feature 4) calls the vendored hook at two explicit
|
|
54
|
+
# points: the captured adopted run, and the hook's own implicit
|
|
55
|
+
# probe — never implicitly.
|
|
56
|
+
def build_base_document(data)
|
|
57
|
+
return AdoptedBase.build(self, data) if data[:adopted]
|
|
58
|
+
|
|
59
|
+
implicit = AdoptedBase.implicit(self, data)
|
|
60
|
+
return implicit if implicit
|
|
61
|
+
|
|
62
|
+
identifier = locate_identifier_klass(data).new
|
|
63
|
+
assign_attributes(identifier, data)
|
|
64
|
+
identifier
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The class each reference selects: the type-position token
|
|
68
|
+
# first, then the publisher-position token (the publisher-type
|
|
69
|
+
# faces CR/CWA/…), then the token-free default class.
|
|
70
|
+
def locate_identifier_klass(data)
|
|
71
|
+
token = data[:guide] || data[:type]
|
|
72
|
+
token ||= data[:publisher] if TYPE_TOKENS.key?(data[:publisher].to_s)
|
|
73
|
+
name = token ? TYPE_TOKENS.fetch(token.to_s, DEFAULT_TYPE) : DEFAULT_TYPE
|
|
74
|
+
Identifiers.const_get(name)
|
|
75
|
+
end
|
|
7
76
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
77
|
+
# The columns, as the coded builder's cast writes them.
|
|
78
|
+
def assign_attributes(identifier, data)
|
|
79
|
+
if data[:year]
|
|
80
|
+
identifier.date = ::Pubid::Components::Date.new(
|
|
81
|
+
year: data[:year]&.to_s,
|
|
13
82
|
)
|
|
14
83
|
end
|
|
84
|
+
identifier.number = data[:number]&.to_s
|
|
85
|
+
identifier.part = data[:part]&.to_s
|
|
86
|
+
identifier.version = present_string(data[:version])
|
|
87
|
+
if data[:publisher]
|
|
88
|
+
identifier.publisher = ::Pubid::Components::Publisher.new(body: data[:publisher].to_s)
|
|
89
|
+
end
|
|
90
|
+
copubs = Array(data[:copublishers]).map do |row|
|
|
91
|
+
value = row[:copublisher] || row["copublisher"]
|
|
92
|
+
::Pubid::Components::Publisher.new(body: unwrap(value).to_s)
|
|
93
|
+
end
|
|
94
|
+
identifier.copublishers = copubs unless copubs.empty?
|
|
95
|
+
type_token = data[:guide] || data[:type]
|
|
96
|
+
identifier.type = ::Pubid::Components::Type.new(abbr: type_token.to_s) if type_token
|
|
97
|
+
captured = data[:stage]
|
|
98
|
+
return unless captured
|
|
99
|
+
|
|
100
|
+
typed_stage = Builder.stage_by_code(STAGE_MAP.fetch(captured.to_s))
|
|
101
|
+
identifier.typed_stage = typed_stage
|
|
102
|
+
identifier.type = typed_stage.to_type
|
|
103
|
+
identifier.stage = typed_stage.to_stage
|
|
104
|
+
end
|
|
15
105
|
|
|
16
|
-
|
|
106
|
+
# A capture the grammar bound as an empty string stores as nil:
|
|
107
|
+
# to_hash drops the empty string, so from_hash would give nil and
|
|
108
|
+
# the two identifiers would not be ==.
|
|
109
|
+
def present_string(value)
|
|
110
|
+
str = unwrap(value)&.to_s
|
|
111
|
+
str unless str.nil? || str.empty?
|
|
112
|
+
end
|
|
17
113
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
114
|
+
# The run's internal captures bind only through the item row, so
|
|
115
|
+
# unbound ones arrive as raw leaves — a Parsanol::Slice or a
|
|
116
|
+
# {value:, offset: ...} span. Unwrap the captured value.
|
|
117
|
+
def unwrap(capture)
|
|
118
|
+
return capture.content if capture.is_a?(Parsanol::Slice)
|
|
119
|
+
return capture[:value] if capture.is_a?(Hash) && capture.key?(:value)
|
|
24
120
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
121
|
+
capture
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The wrapper classes by kind face: the run's dispatch.
|
|
125
|
+
SUPPLEMENT_WRAPPERS = {
|
|
126
|
+
"A" => Identifiers::Amendment,
|
|
127
|
+
"AC" => Identifiers::Corrigendum,
|
|
128
|
+
}.freeze
|
|
129
|
+
|
|
130
|
+
def wrap_supplements(base, data)
|
|
131
|
+
rows = Array(data[:supplements])
|
|
132
|
+
return base if rows.empty?
|
|
133
|
+
|
|
134
|
+
items = rows.map do |row|
|
|
135
|
+
inner = row[:supplement] || row["supplement"] || {}
|
|
136
|
+
inner = inner.values.first if inner.values.all?(Hash)
|
|
137
|
+
inner
|
|
28
138
|
end
|
|
139
|
+
if items.any? { |item| item[:supplements_plus] || item["supplements_plus"] }
|
|
140
|
+
members = items.map { |item| supplement_from(item) }
|
|
141
|
+
Identifiers::ConsolidatedIdentifier.new(identifiers: [base] + members)
|
|
142
|
+
else
|
|
143
|
+
supplement_from(items.first, base: base)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def supplement_from(item, base: nil)
|
|
148
|
+
kind = unwrap(item[:supplement_kind] || item["supplement_kind"])
|
|
149
|
+
number = unwrap(item[:supplement_number] || item["supplement_number"])
|
|
150
|
+
attrs = {}
|
|
151
|
+
attrs[:base] = base if base
|
|
152
|
+
attrs[:number] = present_string(number)
|
|
153
|
+
attrs[:year] = present_string(unwrap(item[:supplements_year] || item["supplements_year"]))
|
|
154
|
+
attrs[:month] = present_string(unwrap(item[:supplements_month] || item["supplements_month"]))
|
|
155
|
+
SUPPLEMENT_WRAPPERS.fetch(kind.to_s).new(**attrs)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def wrap_fragment(identifier, data)
|
|
159
|
+
return identifier unless data[:fragment_number]
|
|
160
|
+
|
|
161
|
+
Identifiers::Fragment.new(
|
|
162
|
+
number: unwrap(data[:fragment_number]).to_s, base: identifier,
|
|
163
|
+
)
|
|
29
164
|
end
|
|
30
165
|
end
|
|
31
166
|
end
|
|
32
167
|
|
|
33
168
|
Pubid::CenPfs::Builder.prepend(Pubid::Builder::AllPartsWrap)
|
|
169
|
+
end
|
|
@@ -1,64 +1,122 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
module Pubid
|
|
2
|
+
module CenPfs
|
|
3
|
+
# The shared base of every concrete CenPfs class. The
|
|
4
|
+
# serialization is the shared ::Pubid::Identifier's (the coded
|
|
5
|
+
# flavor's own shape): flat publisher/copublishers scalars, the
|
|
6
|
+
# date flattened to its fields, and the draft stage compacted
|
|
7
|
+
# to its code by the class hooks below.
|
|
8
|
+
class Identifier < ::Pubid::Identifier
|
|
9
|
+
# The ETSI-style version suffix ("EN 302 326-2 V2.2.1").
|
|
10
|
+
attribute :version, :string
|
|
11
|
+
|
|
12
|
+
# A published document holds no type, stage or typed stage, so
|
|
13
|
+
# a nil one means "published", not "any stage": `EN 1325` is
|
|
14
|
+
# not `prEN 1325`. The three components repeat one entry of
|
|
15
|
+
# the stage registry, so they move together.
|
|
16
|
+
subset_strict :type, :stage, :typed_stage
|
|
17
|
+
|
|
18
|
+
# The bodies that print as a publisher prefix. The publisher
|
|
19
|
+
# attribute also holds the deliverable types that ARE the
|
|
20
|
+
# designation ("CR 954"); those stay under
|
|
21
|
+
# with_publisher: false, as `BS` does for BSI.
|
|
22
|
+
PUBLISHER_PREFIXES = ["CEN", "CLC", "CENELEC", "EN", "CWA", "HD", "ES", "CR", "ENV"].freeze
|
|
23
|
+
|
|
24
|
+
def publisher_prefixes
|
|
25
|
+
super.select do |prefix|
|
|
26
|
+
PUBLISHER_PREFIXES.include?(prefix.split("/").first)
|
|
17
27
|
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The publisher serializes as a bare string, the copublishers
|
|
31
|
+
# as a list of strings (the coded flavor's flat-scalar table).
|
|
32
|
+
def self.flat_scalar_components
|
|
33
|
+
super.merge(publisher: "publisher", copublishers: "copublishers")
|
|
34
|
+
end
|
|
18
35
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
end
|
|
36
|
+
def self.flat_scalar_fields
|
|
37
|
+
super.merge(publisher: :body, copublishers: :body)
|
|
38
|
+
end
|
|
23
39
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
# The declared draft stages: the serialized code is the key,
|
|
41
|
+
# the official-URN code the declared `urn:` value.
|
|
42
|
+
STAGE_CODES = {
|
|
43
|
+
"pren" => { stage_code: :proposal },
|
|
44
|
+
"fpren" => { stage_code: :final_proposal },
|
|
45
|
+
}.freeze
|
|
27
46
|
|
|
28
|
-
|
|
29
|
-
|
|
47
|
+
# A draft stage serializes as its typed-stage code
|
|
48
|
+
# ("stage" => "pren"); #inflate_scalar_components rebuilds
|
|
49
|
+
# the three components from it (the coded compact-hash shape).
|
|
50
|
+
def self.compact_hash(model, hash)
|
|
51
|
+
stage = model.typed_stage
|
|
52
|
+
return unless stage && hash.key?("typed_stage")
|
|
53
|
+
return unless model.type == stage.to_type &&
|
|
54
|
+
model.stage == stage.to_stage
|
|
30
55
|
|
|
31
|
-
|
|
32
|
-
|
|
56
|
+
hash.delete("type")
|
|
57
|
+
hash.delete("typed_stage")
|
|
58
|
+
hash["stage"] = stage.code.to_s
|
|
59
|
+
end
|
|
33
60
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
61
|
+
# Reads the short stage form back; the nested form still
|
|
62
|
+
# reads as before.
|
|
63
|
+
def self.inflate_scalar_components(data)
|
|
64
|
+
data = super
|
|
65
|
+
return data unless data.is_a?(::Hash) && component_attribute?(:stage)
|
|
37
66
|
|
|
38
|
-
|
|
67
|
+
key = data.key?("stage") ? "stage" : :stage
|
|
68
|
+
value = data[key]
|
|
69
|
+
return data unless value.is_a?(::String) || value.is_a?(::Symbol)
|
|
39
70
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
stage = locate_stage_by_code(value)
|
|
72
|
+
return data unless stage
|
|
73
|
+
|
|
74
|
+
data = data.dup
|
|
75
|
+
data.delete(key)
|
|
76
|
+
data["type"] = stage.to_type.to_hash
|
|
77
|
+
data["stage"] = stage.to_stage.to_hash
|
|
78
|
+
data["typed_stage"] = stage.to_hash
|
|
79
|
+
data
|
|
80
|
+
end
|
|
46
81
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
82
|
+
# The stage entry by serialized code, or nil.
|
|
83
|
+
def self.locate_stage_by_code(code)
|
|
84
|
+
Builder.stage_by_code(code.to_s)
|
|
85
|
+
end
|
|
52
86
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
87
|
+
# The draft stage's own code is the MR type segment
|
|
88
|
+
# ("en.pren.10160"); a published identifier has no typed
|
|
89
|
+
# stage, so the shared hook answers nil.
|
|
90
|
+
def mr_type
|
|
91
|
+
stage = typed_stage
|
|
92
|
+
return super unless stage && stage.stage_code.to_s != "published"
|
|
56
93
|
|
|
57
|
-
|
|
94
|
+
stage.code.to_s.downcase
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.parse(identifier)
|
|
98
|
+
unless identifier.is_a?(String)
|
|
99
|
+
raise Pubid::Errors::InvalidInputError,
|
|
100
|
+
Pubid::INPUT_NOT_A_STRING_MESSAGE
|
|
101
|
+
end
|
|
58
102
|
|
|
59
|
-
|
|
60
|
-
|
|
103
|
+
if identifier.length > Pubid::MAX_INPUT_LENGTH
|
|
104
|
+
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
61
105
|
end
|
|
106
|
+
|
|
107
|
+
identifier = ::Pubid.utf8_input(identifier)
|
|
108
|
+
|
|
109
|
+
parsed = Pubid::Parg::Model.bound(:cen_pfs, identifier)
|
|
110
|
+
Builder.build(parsed)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
# "amd.1.2005" from ("amd", "1", 2005). A nil or blank
|
|
116
|
+
# segment is left out.
|
|
117
|
+
def mr_join_segments(*segments)
|
|
118
|
+
segments.map(&:to_s).reject(&:empty?).join(".").downcase
|
|
62
119
|
end
|
|
63
120
|
end
|
|
64
121
|
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Pubid
|
|
2
|
+
module CenPfs
|
|
3
|
+
module Identifiers
|
|
4
|
+
# Concrete Adopted European Norm identifier type (PFS type `adopted_european_norm`).
|
|
5
|
+
class AdoptedEuropeanNorm < Identifier
|
|
6
|
+
TYPED_STAGES = [
|
|
7
|
+
].freeze
|
|
8
|
+
|
|
9
|
+
def self.type
|
|
10
|
+
{ key: :aen, web: :adopted_european_norm, title: "Adopted European Norm", short: "EN" }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The publisher default (feature 5): the type's own token
|
|
14
|
+
# when the type is its designation, else the scheme default.
|
|
15
|
+
def self.default_publisher
|
|
16
|
+
::Pubid::Components::Publisher.new(body: "EN")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attribute :publisher, ::Pubid::Components::Publisher, default: -> { self.class.default_publisher }
|
|
20
|
+
attribute :type, ::Pubid::Components::Type, default: -> { self.class.default_type }
|
|
21
|
+
|
|
22
|
+
def self.default_type
|
|
23
|
+
::Pubid::Components::Type.new(abbr: type[:short])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The adopted document (feature 4): the whole identity lives
|
|
27
|
+
# on the nested ISO/IEC identifier.
|
|
28
|
+
attribute :adopted, ::Pubid::Identifier, polymorphic: true
|
|
29
|
+
|
|
30
|
+
def root
|
|
31
|
+
adopted ? adopted.root : self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# MR "cen.iso.ts.21003-7": the publisher group fused, then the
|
|
35
|
+
# adopted document's own MR string in place of a number.
|
|
36
|
+
def mr_publisher
|
|
37
|
+
mr_join(*([publisher] + Array(copublishers)).compact
|
|
38
|
+
.map { |p| p.to_s.downcase })
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def mr_number_with_part
|
|
42
|
+
adopted ? adopted.to_mr_string : super
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The scheme-canonical wire tag declared in the definition:
|
|
46
|
+
# to_hash stays byte-identical with the scheme's coded
|
|
47
|
+
# flavor, and this flavor's own from_hash routes the tag
|
|
48
|
+
# to this class.
|
|
49
|
+
def self.polymorphic_name
|
|
50
|
+
"pubid:cencenelec:adopted-european-norm"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Pubid
|
|
2
|
+
module CenPfs
|
|
3
|
+
module Identifiers
|
|
4
|
+
# The synthesized Amendment wrapper (PFS supplements
|
|
5
|
+
# declaration, issue #121 feature 3): the kind face
|
|
6
|
+
# "A" is the run's dispatch token and the
|
|
7
|
+
# printed notation's face.
|
|
8
|
+
class Amendment < Supplement
|
|
9
|
+
FACE = "A".freeze
|
|
10
|
+
|
|
11
|
+
def self.face
|
|
12
|
+
FACE
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.polymorphic_name
|
|
16
|
+
"pubid:cencenelec:amendment"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The MR slug's supplement suffix: the shared MrString
|
|
20
|
+
# recurses into base and appends this.
|
|
21
|
+
def mr_supplement_suffix
|
|
22
|
+
mr_join_segments("amd", number, year)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Pubid
|
|
2
|
+
module CenPfs
|
|
3
|
+
module Identifiers
|
|
4
|
+
# Concrete CEN Report identifier type (PFS type `cen_report`).
|
|
5
|
+
class CenReport < Identifier
|
|
6
|
+
TYPED_STAGES = [
|
|
7
|
+
].freeze
|
|
8
|
+
|
|
9
|
+
def self.type
|
|
10
|
+
{ key: :cr, web: :cen_report, title: "CEN Report", short: "CR" }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The publisher default (feature 5): the type's own token
|
|
14
|
+
# when the type is its designation, else the scheme default.
|
|
15
|
+
def self.default_publisher
|
|
16
|
+
::Pubid::Components::Publisher.new(body: "CR")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attribute :publisher, ::Pubid::Components::Publisher, default: -> { self.class.default_publisher }
|
|
20
|
+
attribute :type, ::Pubid::Components::Type, default: -> { self.class.default_type }
|
|
21
|
+
|
|
22
|
+
def self.default_type
|
|
23
|
+
::Pubid::Components::Type.new(abbr: type[:short])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The scheme-canonical wire tag declared in the definition:
|
|
27
|
+
# to_hash stays byte-identical with the scheme's coded
|
|
28
|
+
# flavor, and this flavor's own from_hash routes the tag
|
|
29
|
+
# to this class.
|
|
30
|
+
def self.polymorphic_name
|
|
31
|
+
"pubid:cencenelec:cen-report"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Pubid
|
|
2
|
+
module CenPfs
|
|
3
|
+
module Identifiers
|
|
4
|
+
# Concrete CEN Workshop Agreement identifier type (PFS type `cen_workshop_agreement`).
|
|
5
|
+
class CenWorkshopAgreement < Identifier
|
|
6
|
+
TYPED_STAGES = [
|
|
7
|
+
].freeze
|
|
8
|
+
|
|
9
|
+
def self.type
|
|
10
|
+
{ key: :cwa, web: :cen_workshop_agreement, title: "CEN Workshop Agreement", short: "CWA" }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The publisher default (feature 5): the type's own token
|
|
14
|
+
# when the type is its designation, else the scheme default.
|
|
15
|
+
def self.default_publisher
|
|
16
|
+
::Pubid::Components::Publisher.new(body: "CWA")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attribute :publisher, ::Pubid::Components::Publisher, default: -> { self.class.default_publisher }
|
|
20
|
+
attribute :type, ::Pubid::Components::Type, default: -> { self.class.default_type }
|
|
21
|
+
|
|
22
|
+
def self.default_type
|
|
23
|
+
::Pubid::Components::Type.new(abbr: type[:short])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The scheme-canonical wire tag declared in the definition:
|
|
27
|
+
# to_hash stays byte-identical with the scheme's coded
|
|
28
|
+
# flavor, and this flavor's own from_hash routes the tag
|
|
29
|
+
# to this class.
|
|
30
|
+
def self.polymorphic_name
|
|
31
|
+
"pubid:cencenelec:cen-workshop-agreement"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Pubid
|
|
2
|
+
module CenPfs
|
|
3
|
+
module Identifiers
|
|
4
|
+
# The consolidated (plus-run) bundle: the base document is
|
|
5
|
+
# the first member; the supplements carry no base.
|
|
6
|
+
class ConsolidatedIdentifier < ::Pubid::CenPfs::Identifier
|
|
7
|
+
attribute :identifiers, ::Pubid::CenPfs::Identifier,
|
|
8
|
+
polymorphic: true, collection: true
|
|
9
|
+
|
|
10
|
+
# The origin document. Members live in `identifiers`, so
|
|
11
|
+
# walk the first member to its root.
|
|
12
|
+
def root
|
|
13
|
+
identifiers&.first&.root || self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def base_document
|
|
17
|
+
identifiers&.first&.base_document || self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def drop_supplements
|
|
21
|
+
identifiers&.first || self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The base document, then each member's own supplement
|
|
25
|
+
# suffix behind the plus marker — the coded shape.
|
|
26
|
+
def to_mr_string
|
|
27
|
+
base, *supplements = identifiers
|
|
28
|
+
return "" unless base
|
|
29
|
+
|
|
30
|
+
([base.to_mr_string] +
|
|
31
|
+
supplements.map { |s| "plus-#{s.mr_supplement_suffix}" })
|
|
32
|
+
.join(::Pubid::Renderers::MrString::SEPARATOR_SUPPLEMENT)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.polymorphic_name
|
|
36
|
+
"pubid:cencenelec:consolidated-identifier"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|