pubid 2.0.0.pre.alpha.29 → 2.0.0.pre.alpha.30
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/parg/cen_cenelec.json +1 -1
- data/data/parg/cen_cenelec.parg +12 -4
- data/data/parg/evs.json +1 -1
- data/data/parg/ieee.json +1 -1
- data/lib/pubid/cen_cenelec/builder.rb +10 -0
- data/lib/pubid/cen_cenelec/identifier.rb +2 -0
- data/lib/pubid/cen_cenelec/renderer.rb +4 -1
- data/lib/pubid/evs/builder.rb +77 -9
- data/lib/pubid/evs/identifier.rb +29 -7
- data/lib/pubid/evs/identifiers/estonian_standard.rb +15 -0
- data/lib/pubid/evs/identifiers/international_adoption.rb +16 -0
- data/lib/pubid/evs/identifiers.rb +2 -0
- data/lib/pubid/evs/renderer.rb +43 -10
- data/lib/pubid/evs/urn_generator.rb +46 -6
- data/lib/pubid/evs/urn_parser.rb +94 -37
- data/lib/pubid/evs.rb +13 -2
- data/lib/pubid/ieee/builder.rb +63 -3
- data/lib/pubid/ieee/identifiers/base.rb +7 -1
- data/lib/pubid/ieee/renderer.rb +6 -2
- data/lib/pubid/version.rb +1 -1
- metadata +4 -2
|
@@ -18,6 +18,13 @@ module Pubid
|
|
|
18
18
|
return build_fragment_identifier(data)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
+
# "TR" is a CEN or CLC technical report ("CEN/TR", "CLC/TR") — the
|
|
22
|
+
# EN publisher never carries it (pubid ruling 2026-10-13).
|
|
23
|
+
if data[:type].to_s == "TR" && %w[EN].include?(data[:publisher].to_s)
|
|
24
|
+
raise Pubid::Errors::ParseError,
|
|
25
|
+
"\"EN\" cannot be \"TR\": a technical report is \"CEN/TR\" or \"CLC/TR\""
|
|
26
|
+
end
|
|
27
|
+
|
|
21
28
|
# Every base document (an adopted norm, an ENV adoption, a plain
|
|
22
29
|
# identifier) goes through the same supplement wrapping. The adopted
|
|
23
30
|
# branches used to return early, so "CEN ISO/TS 21003-7:2008/A1:2010"
|
|
@@ -171,6 +178,9 @@ module Pubid
|
|
|
171
178
|
when :part
|
|
172
179
|
value[:part].to_s if value.is_a?(Hash)
|
|
173
180
|
|
|
181
|
+
when :version
|
|
182
|
+
value.to_s unless value.to_s.empty?
|
|
183
|
+
|
|
174
184
|
when :year, :date
|
|
175
185
|
# Check if month is present in the data and include it
|
|
176
186
|
month_val = @data[:month]&.to_s
|
|
@@ -19,6 +19,8 @@ module Pubid
|
|
|
19
19
|
attribute :number, :string
|
|
20
20
|
attribute :part, :string
|
|
21
21
|
attribute :subpart, :string
|
|
22
|
+
# The ETSI-style version suffix ("EN 302 326-2 V2.2.1")
|
|
23
|
+
attribute :version, :string
|
|
22
24
|
|
|
23
25
|
# A published European Norm holds no type, stage or typed stage, so a
|
|
24
26
|
# nil one means "published", not "any stage": `EN 1325` is not
|
|
@@ -110,12 +110,15 @@ module Pubid
|
|
|
110
110
|
parts << "Guide"
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
# Number with part (which may be multi-level like "5-1-1")
|
|
113
|
+
# Number with part (which may be multi-level like "5-1-1") and the
|
|
114
|
+
# ETSI-style version suffix ("EN 302 326-2 V2.2.1")
|
|
114
115
|
if id.number
|
|
115
116
|
number_str = id.number
|
|
116
117
|
if id.part
|
|
117
118
|
number_str += "-#{id.part}"
|
|
118
119
|
end
|
|
120
|
+
# the version capture carries its own leading space
|
|
121
|
+
number_str += id.version if id.version
|
|
119
122
|
parts << number_str
|
|
120
123
|
end
|
|
121
124
|
|
data/lib/pubid/evs/builder.rb
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Evs
|
|
5
|
-
# Builds an EVS
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
5
|
+
# Builds an EVS identifier from the composed parse tree. The structured
|
|
6
|
+
# :adopted subtree (produced by the embedded CEN grammar) goes straight
|
|
7
|
+
# to the CEN/CENELEC builder as data — no string re-serialization
|
|
8
|
+
# anywhere; the ISO/IEC and plain-Estonian shapes carry their captures
|
|
9
|
+
# directly.
|
|
9
10
|
class Builder < Pubid::Builder::Base
|
|
10
11
|
def self.build(parsed_data)
|
|
11
12
|
new.build(parsed_data)
|
|
@@ -14,12 +15,79 @@ module Pubid
|
|
|
14
15
|
def build(data)
|
|
15
16
|
data = flatten_array(data) if data.is_a?(Array)
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
identifier =
|
|
19
|
+
if data[:adopted]
|
|
20
|
+
adopted_data = data[:adopted]
|
|
21
|
+
# The adopted TS face carries no TS marker ("EVS-EN
|
|
22
|
+
# 16798-1:2019"; pubid ruling 2026-10-13) — the TS-ness of an
|
|
23
|
+
# EN adoption is metadata, not identity, so the type capture
|
|
24
|
+
# strips before the CEN builder routes on it.
|
|
25
|
+
if adopted_data[:type].to_s == "TS" && adopted_data[:publisher].to_s == "EN"
|
|
26
|
+
adopted_data = adopted_data.reject { |k, _| k == :type }
|
|
27
|
+
end
|
|
28
|
+
Identifiers::NationalAdoption.new(
|
|
29
|
+
base: Pubid::CenCenelec::Builder.build(adopted_data),
|
|
30
|
+
**shared(data),
|
|
31
|
+
)
|
|
32
|
+
elsif data[:intl]
|
|
33
|
+
intl = data[:intl]
|
|
34
|
+
Identifiers::InternationalAdoption.new(
|
|
35
|
+
intl_org: extract(intl[:intl_org]),
|
|
36
|
+
number: extract(intl[:number]),
|
|
37
|
+
part: extract(intl[:part]),
|
|
38
|
+
year: extract(intl[:year]),
|
|
39
|
+
**shared(data),
|
|
40
|
+
)
|
|
41
|
+
else
|
|
42
|
+
plain = data[:plain]
|
|
43
|
+
Identifiers::EstonianStandard.new(
|
|
44
|
+
number: extract(plain[:number]),
|
|
45
|
+
year: extract(plain[:year]),
|
|
46
|
+
evs_type: extract(plain[:evs_type]),
|
|
47
|
+
**shared(data),
|
|
48
|
+
)
|
|
49
|
+
end
|
|
18
50
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
51
|
+
attach_supplements(identifier, data)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def shared(data)
|
|
57
|
+
{
|
|
58
|
+
separator: extract(data[:evs_separator]) || "-",
|
|
59
|
+
draft_prefix: data[:draft_prefix] ? true : false,
|
|
60
|
+
language: extract(data[:language]),
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def attach_supplements(identifier, data)
|
|
65
|
+
if data[:na]
|
|
66
|
+
identifier.na_separator = supplement_separator(data[:na])
|
|
67
|
+
identifier.na_year = extract(data[:na][:na_year])
|
|
68
|
+
end
|
|
69
|
+
if data[:evs_amd]
|
|
70
|
+
identifier.amd_separator = supplement_separator(data[:evs_amd])
|
|
71
|
+
identifier.amd_kind = extract(data[:evs_amd][:amd_kind])
|
|
72
|
+
identifier.amd_number = extract(data[:evs_amd][:amd_number])
|
|
73
|
+
identifier.amd_year = extract(data[:evs_amd][:amd_year])
|
|
74
|
+
end
|
|
75
|
+
if data[:evs_cor]
|
|
76
|
+
identifier.cor_separator = supplement_separator(data[:evs_cor])
|
|
77
|
+
identifier.cor_kind = extract(data[:evs_cor][:cor_kind])
|
|
78
|
+
identifier.cor_number = extract(data[:evs_cor][:cor_number])
|
|
79
|
+
identifier.cor_year = extract(data[:evs_cor][:cor_year])
|
|
80
|
+
end
|
|
81
|
+
identifier
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def supplement_separator(supp)
|
|
85
|
+
%i[supp_plus supp_slash supp_space]
|
|
86
|
+
.map { |k| extract(supp[k]) }.compact.first
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def extract(value)
|
|
90
|
+
value.is_a?(Hash) ? value[:value] : value
|
|
23
91
|
end
|
|
24
92
|
end
|
|
25
93
|
end
|
data/lib/pubid/evs/identifier.rb
CHANGED
|
@@ -2,19 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Evs
|
|
5
|
-
# An EVS
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# An EVS identifier: the national prefix ("EVS" / "prEVS") over a CEN
|
|
6
|
+
# adoption, an ISO/IEC adoption, or a plain Estonian standard, plus the
|
|
7
|
+
# EVS-list supplements and the language code.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
# "EVS-EN 18216:2026" (base: EN 18216:2026)
|
|
11
|
-
# "EVS-EN ISO 14001:2026" (base: EN ISO 14001:2026)
|
|
12
|
-
# "EVS-EN ISO/IEC 27017:2026" (base: EN ISO/IEC 27017:2026)
|
|
9
|
+
# "EVS-EN 18216:2026" (base: EN 18216:2026)
|
|
13
10
|
# "EVS-EN ISO 9001:2015/A1:2024" (base: EN ISO 9001:2015/A1:2024)
|
|
11
|
+
# "EVS-ISO 9001:2015" (intl_org: ISO)
|
|
12
|
+
# "EVS-IEC 62368-1:2020" (intl_org: IEC)
|
|
13
|
+
# "EVS 921:2019", "EVS-TS 1:2019"
|
|
14
|
+
#
|
|
15
|
+
# Shared supplements (pubid#517-list): the national annex ("+NA:2010" /
|
|
16
|
+
# "/NA"), the AMD and COR spellings, and the trailing language code
|
|
17
|
+
# (" et" / " en" / " et-en"). The supplement separators are semantic
|
|
18
|
+
# ("+X" a full standard plus the supplement, "/X" the supplement only)
|
|
19
|
+
# and are preserved as captured.
|
|
14
20
|
class Identifier < ::Pubid::Identifier
|
|
15
21
|
attribute :base, ::Pubid::Identifier, polymorphic: true
|
|
16
22
|
# "-" or " " — preserves the printed separator ("EVS-EN" vs "EVS EN")
|
|
17
23
|
attribute :separator, :string, default: -> { "-" }
|
|
24
|
+
attribute :draft_prefix, :boolean, default: -> { false }
|
|
25
|
+
attribute :language, :string
|
|
26
|
+
|
|
27
|
+
# National annex: separator ("+"/"/") and optional year
|
|
28
|
+
attribute :na_separator, :string
|
|
29
|
+
attribute :na_year, :string
|
|
30
|
+
# AMD amendment spelling
|
|
31
|
+
attribute :amd_separator, :string
|
|
32
|
+
attribute :amd_kind, :string
|
|
33
|
+
attribute :amd_number, :string
|
|
34
|
+
attribute :amd_year, :string
|
|
35
|
+
# COR corrigendum spelling
|
|
36
|
+
attribute :cor_separator, :string
|
|
37
|
+
attribute :cor_kind, :string
|
|
38
|
+
attribute :cor_number, :string
|
|
39
|
+
attribute :cor_year, :string
|
|
18
40
|
|
|
19
41
|
def to_s(**opts)
|
|
20
42
|
render(format: :human, **opts)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pubid
|
|
4
|
+
module Evs
|
|
5
|
+
module Identifiers
|
|
6
|
+
# A plain Estonian standard: "EVS 921:2019", optionally carrying the
|
|
7
|
+
# TS/TR type ("EVS-TS 1:2019").
|
|
8
|
+
class EstonianStandard < ::Pubid::Evs::Identifier
|
|
9
|
+
attribute :number, :string
|
|
10
|
+
attribute :year, :string
|
|
11
|
+
attribute :evs_type, :string
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pubid
|
|
4
|
+
module Evs
|
|
5
|
+
module Identifiers
|
|
6
|
+
# The EVS adoption of an ISO or IEC standard directly (not via CEN).
|
|
7
|
+
# Examples: "EVS-ISO 9001:2015", "EVS-IEC 62368-1:2020"
|
|
8
|
+
class InternationalAdoption < ::Pubid::Evs::Identifier
|
|
9
|
+
attribute :intl_org, :string
|
|
10
|
+
attribute :number, :string
|
|
11
|
+
attribute :part, :string
|
|
12
|
+
attribute :year, :string
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -4,6 +4,8 @@ module Pubid
|
|
|
4
4
|
module Evs
|
|
5
5
|
module Identifiers
|
|
6
6
|
autoload :NationalAdoption, "#{__dir__}/identifiers/national_adoption"
|
|
7
|
+
autoload :InternationalAdoption, "#{__dir__}/identifiers/international_adoption"
|
|
8
|
+
autoload :EstonianStandard, "#{__dir__}/identifiers/estonian_standard"
|
|
7
9
|
end
|
|
8
10
|
end
|
|
9
11
|
end
|
data/lib/pubid/evs/renderer.rb
CHANGED
|
@@ -4,26 +4,59 @@ module Pubid
|
|
|
4
4
|
module Evs
|
|
5
5
|
# Human-readable renderer for EVS identifiers.
|
|
6
6
|
#
|
|
7
|
-
# Produces strings like:
|
|
8
7
|
# "EVS-EN 18216:2026"
|
|
9
|
-
# "EVS-EN ISO 14001:2026"
|
|
10
|
-
# "EVS-EN ISO/IEC 27017:2026"
|
|
11
8
|
# "EVS-EN ISO 9001:2015/A1:2024"
|
|
9
|
+
# "EVS-ISO 9001:2015"
|
|
10
|
+
# "EVS-IEC 62368-1:2020"
|
|
11
|
+
# "EVS 921:2019"
|
|
12
|
+
# "EVS-TS 1:2019"
|
|
13
|
+
# "prEVS-EN 100:2026"
|
|
14
|
+
# "EVS-EN 1993-1-1:2005+NA:2010 et"
|
|
12
15
|
class Renderer < ::Pubid::Renderers::Base
|
|
13
16
|
def render(context: nil, **_opts)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"EVS#{separator}
|
|
17
|
+
result = +""
|
|
18
|
+
result << "pr" if @id.draft_prefix
|
|
19
|
+
result << "EVS#{@id.separator}"
|
|
20
|
+
result << body
|
|
21
|
+
result << supplement(@id.amd_separator, @id.amd_kind, @id.amd_number, @id.amd_year)
|
|
22
|
+
result << supplement(@id.cor_separator, @id.cor_kind || "COR", @id.cor_number, @id.cor_year)
|
|
23
|
+
result << supplement(@id.na_separator, "NA", nil, @id.na_year)
|
|
24
|
+
result << " #{@id.language}" if @id.language
|
|
25
|
+
result
|
|
17
26
|
end
|
|
18
27
|
|
|
19
28
|
private
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
# The adopted CEN identifier renders with its own "EN …" prefix; the
|
|
31
|
+
# ISO/IEC and plain shapes compose from their captures.
|
|
32
|
+
def body
|
|
33
|
+
case @id
|
|
34
|
+
when Pubid::Evs::Identifiers::NationalAdoption
|
|
35
|
+
@id.base.to_s
|
|
36
|
+
when Pubid::Evs::Identifiers::InternationalAdoption
|
|
37
|
+
org = "#{@id.intl_org} #{@id.number}"
|
|
38
|
+
org << "-#{@id.part}" if @id.part
|
|
39
|
+
org << ":#{@id.year}" if @id.year
|
|
40
|
+
org
|
|
41
|
+
else
|
|
42
|
+
plain = +""
|
|
43
|
+
plain << "#{@id.evs_type} " if @id.evs_type
|
|
44
|
+
plain << @id.number.to_s
|
|
45
|
+
plain << ":#{@id.year}" if @id.year
|
|
46
|
+
plain
|
|
47
|
+
end
|
|
23
48
|
end
|
|
24
49
|
|
|
25
|
-
|
|
26
|
-
|
|
50
|
+
# The separator is semantic ("+X" a full standard plus the
|
|
51
|
+
# supplement, "/X" the supplement only, " AMD" the space spelling)
|
|
52
|
+
# and prints as captured.
|
|
53
|
+
def supplement(separator, kind, number, year)
|
|
54
|
+
return "" unless separator
|
|
55
|
+
|
|
56
|
+
face = +"#{separator}#{kind}"
|
|
57
|
+
face << number.to_s if number
|
|
58
|
+
face << ":#{year}" if year
|
|
59
|
+
face
|
|
27
60
|
end
|
|
28
61
|
end
|
|
29
62
|
end
|
|
@@ -8,22 +8,62 @@ module Pubid
|
|
|
8
8
|
# "EVS-EN ISO 14001:2026" → urn:evs:en:iso:14001:2026
|
|
9
9
|
# "EVS-EN ISO/IEC 27017:2026" → urn:evs:en:iso-iec:27017:2026
|
|
10
10
|
# "EVS-EN ISO 9001:2015/A1:2024" → urn:evs:en:iso:9001:2015:amd:1:2024
|
|
11
|
+
# "EVS-ISO 9001:2015" → urn:evs:iso:9001:2015
|
|
12
|
+
# "EVS-IEC 62368-1:2020" → urn:evs:iec:62368-1:2020
|
|
13
|
+
# "EVS 921:2019" → urn:evs:921:2019
|
|
14
|
+
# "EVS-TS 1:2019" → urn:evs:ts:1:2019
|
|
11
15
|
#
|
|
12
16
|
# Generation delegates to the adopted CEN identifier's URN and swaps the
|
|
13
17
|
# `urn:cen:` namespace for `urn:evs:`, so the two stay consistent by
|
|
14
|
-
# construction.
|
|
18
|
+
# construction; the ISO/IEC and plain shapes compose from their captures.
|
|
15
19
|
class UrnGenerator < Pubid::UrnGenerator::Base
|
|
16
20
|
CEN_NAMESPACE = "urn:cen:"
|
|
17
21
|
EVS_NAMESPACE = "urn:evs:"
|
|
18
22
|
|
|
19
23
|
def generate
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
segments = +""
|
|
25
|
+
segments << "pr:" if identifier.draft_prefix
|
|
26
|
+
|
|
27
|
+
segments << if identifier.is_a?(Identifiers::NationalAdoption)
|
|
28
|
+
adopted_urn = identifier.base.to_urn
|
|
29
|
+
unless adopted_urn.start_with?(CEN_NAMESPACE)
|
|
30
|
+
raise Errors::ParseError,
|
|
31
|
+
"expected adopted CEN URN, got #{adopted_urn.inspect}"
|
|
32
|
+
end
|
|
33
|
+
adopted_urn.delete_prefix(CEN_NAMESPACE)
|
|
34
|
+
else
|
|
35
|
+
body = +""
|
|
36
|
+
if identifier.is_a?(Identifiers::InternationalAdoption)
|
|
37
|
+
body << "#{identifier.intl_org.downcase}:"
|
|
38
|
+
body << identifier.number.to_s
|
|
39
|
+
body << "-#{identifier.part}" if identifier.part
|
|
40
|
+
else
|
|
41
|
+
body << "#{identifier.evs_type.downcase}:" if identifier.evs_type
|
|
42
|
+
body << identifier.number.to_s
|
|
43
|
+
end
|
|
44
|
+
body << ":#{identifier.year}" if identifier.year
|
|
45
|
+
body
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
segments << ":na" << ":#{identifier.na_year}" if identifier.na_separator && identifier.na_year
|
|
49
|
+
segments << ":na" if identifier.na_separator && !identifier.na_year
|
|
50
|
+
if identifier.amd_separator
|
|
51
|
+
# the consolidation marker mirrors the cen vocabulary: "+A1" is
|
|
52
|
+
# "plus:amd:1", the slash and space spellings carry no marker
|
|
53
|
+
segments << ":plus" if identifier.amd_separator == "+"
|
|
54
|
+
segments << ":amd"
|
|
55
|
+
segments << ":#{identifier.amd_number}" if identifier.amd_number
|
|
56
|
+
segments << ":#{identifier.amd_year}" if identifier.amd_year
|
|
57
|
+
end
|
|
58
|
+
if identifier.cor_separator
|
|
59
|
+
segments << ":plus" if identifier.cor_separator == "+"
|
|
60
|
+
segments << ":cor"
|
|
61
|
+
segments << ":#{identifier.cor_number}" if identifier.cor_number
|
|
62
|
+
segments << ":#{identifier.cor_year}" if identifier.cor_year
|
|
24
63
|
end
|
|
64
|
+
segments << ":#{identifier.language}" if identifier.language
|
|
25
65
|
|
|
26
|
-
|
|
66
|
+
"#{EVS_NAMESPACE}#{segments}"
|
|
27
67
|
end
|
|
28
68
|
end
|
|
29
69
|
end
|
data/lib/pubid/evs/urn_parser.rb
CHANGED
|
@@ -3,14 +3,19 @@
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Evs
|
|
5
5
|
# Parses `urn:evs:…` URNs back into identifiers, following the same
|
|
6
|
-
# pattern as Pubid::CenCenelec::UrnParser: reassemble the
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# pattern as Pubid::CenCenelec::UrnParser: reassemble the identifier's
|
|
7
|
+
# human text and delegate to the real grammar so every structural check
|
|
8
|
+
# happens in parsers, not in this class.
|
|
9
9
|
#
|
|
10
10
|
# urn:evs:en:18216:2026 → EVS-EN 18216:2026
|
|
11
11
|
# urn:evs:en:iso:14001:2026 → EVS-EN ISO 14001:2026
|
|
12
12
|
# urn:evs:en:iso-iec:27017:2026 → EVS-EN ISO/IEC 27017:2026
|
|
13
13
|
# urn:evs:en:iso:9001:2015:amd:1:2024 → EVS-EN ISO 9001:2015/A1:2024
|
|
14
|
+
# urn:evs:iso:9001:2015 → EVS-ISO 9001:2015
|
|
15
|
+
# urn:evs:921:2019 → EVS 921:2019
|
|
16
|
+
# urn:evs:ts:1:2019 → EVS-TS 1:2019
|
|
17
|
+
# urn:evs:pr:en:100:2026 → prEVS-EN 100:2026
|
|
18
|
+
# urn:evs:en:1993-1-1:2005:na:2010:et → EVS-EN 1993-1-1:2005+NA:2010 et
|
|
14
19
|
class UrnParser < Pubid::UrnParser::Base
|
|
15
20
|
ORGS = {
|
|
16
21
|
"iso" => "ISO",
|
|
@@ -19,6 +24,8 @@ module Pubid
|
|
|
19
24
|
"cispr" => "CISPR",
|
|
20
25
|
}.freeze
|
|
21
26
|
|
|
27
|
+
TYPES = { "ts" => "TS", "tr" => "TR" }.freeze
|
|
28
|
+
|
|
22
29
|
# UrnGenerator emits "urn:evs:" (national-body namespace).
|
|
23
30
|
def flavor_name
|
|
24
31
|
"evs"
|
|
@@ -27,54 +34,104 @@ module Pubid
|
|
|
27
34
|
def parse_urn(urn)
|
|
28
35
|
parts = split_parts(strip_namespace(urn))
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
draft = parts.first == "pr"
|
|
38
|
+
parts.shift if draft
|
|
39
|
+
|
|
40
|
+
# The trailing language code and the EVS-level national annex peel
|
|
41
|
+
# before the body dispatch — both are EVS-level, never cen segments.
|
|
42
|
+
language = parts.last&.match?(/\A(et-en|et|en)\z/) && parts.length > 1 ? parts.pop : nil
|
|
43
|
+
na_year = nil
|
|
44
|
+
na_present = false
|
|
45
|
+
if (na_i = parts.index("na"))
|
|
46
|
+
parts.delete_at(na_i)
|
|
47
|
+
na_present = true
|
|
48
|
+
na_year = parts[na_i]&.match?(/\A\d{4}\z/) ? parts.delete_at(na_i) : nil
|
|
33
49
|
end
|
|
34
50
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
head = parts.shift
|
|
52
|
+
text = +"EVS"
|
|
53
|
+
|
|
54
|
+
case head
|
|
55
|
+
when /\Aen\z/i
|
|
56
|
+
# the adopted CEN identifier: "en [org] number[:year] [supplements]"
|
|
57
|
+
text << "-EN"
|
|
58
|
+
org_parts = []
|
|
59
|
+
org_parts << parts.shift while parts.first&.match?(/\A[a-z][a-z-]*\z/)
|
|
60
|
+
if org_parts.any?
|
|
61
|
+
org = ORGS.fetch(org_parts.join("-")) do
|
|
62
|
+
raise Errors::ParseError, "unknown adopted org in #{urn.inspect}"
|
|
63
|
+
end
|
|
64
|
+
text << " #{org}"
|
|
41
65
|
end
|
|
42
|
-
|
|
43
|
-
|
|
66
|
+
number = parts.shift
|
|
67
|
+
raise Errors::ParseError, "missing number in #{urn.inspect}" unless number
|
|
44
68
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
69
|
+
text << " #{number}"
|
|
70
|
+
year = parts.shift
|
|
71
|
+
text << ":#{year}" if year
|
|
72
|
+
text << adopted_supplement(parts, urn)
|
|
73
|
+
when "ts", "tr"
|
|
74
|
+
number = parts.shift
|
|
75
|
+
year = parts.shift
|
|
76
|
+
text << "-#{TYPES.fetch(head)} #{number}"
|
|
77
|
+
text << ":#{year}" if year
|
|
78
|
+
when "iso", "iec", "cispr"
|
|
79
|
+
# a direct ISO/IEC adoption: "iso:9001[:part]:year"
|
|
80
|
+
org = ORGS.fetch(head)
|
|
81
|
+
number = parts.shift
|
|
82
|
+
part = parts.shift if parts.first&.match?(/\A\d+\z/) && !parts.first&.match?(/\A\d{4}\z/) && parts.length > 1
|
|
83
|
+
year = parts.shift
|
|
84
|
+
text << "-#{org} #{number}"
|
|
85
|
+
text << "-#{part}" if part
|
|
86
|
+
text << ":#{year}" if year
|
|
87
|
+
when /\A\d/
|
|
88
|
+
number = head
|
|
89
|
+
year = parts.shift
|
|
90
|
+
text << " #{number}"
|
|
91
|
+
text << ":#{year}" if year
|
|
92
|
+
else
|
|
93
|
+
raise Errors::ParseError, "unsupported shape in #{urn.inspect}"
|
|
50
94
|
end
|
|
51
95
|
|
|
52
|
-
|
|
53
|
-
text << ":#{year}" if year
|
|
96
|
+
raise Errors::ParseError, "trailing segments in #{urn.inspect}" unless parts.empty?
|
|
54
97
|
|
|
55
|
-
|
|
98
|
+
if na_present
|
|
99
|
+
text << "+NA"
|
|
100
|
+
text << ":#{na_year}" if na_year
|
|
101
|
+
end
|
|
102
|
+
text << " #{language}" if language
|
|
56
103
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
104
|
+
built = Pubid::Evs.parse(text)
|
|
105
|
+
built.draft_prefix = draft
|
|
106
|
+
built
|
|
61
107
|
end
|
|
62
108
|
|
|
63
109
|
private
|
|
64
110
|
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
111
|
+
# The adopted CEN spine consumes its own supplements; their urn
|
|
112
|
+
# segments carry the consolidation marker ("plus" = "+A1", its absence
|
|
113
|
+
# = "/A1") — cen_cenelec/urn_generator.rb's supplement_segments.
|
|
114
|
+
def adopted_supplement(parts, urn)
|
|
115
|
+
faces = +""
|
|
116
|
+
while %w[plus slash amd cor frag].include?(parts.first)
|
|
117
|
+
if %w[plus slash].include?(parts.first)
|
|
118
|
+
sep = parts.shift == "plus" ? "+" : "/"
|
|
119
|
+
kind = parts.shift
|
|
120
|
+
else
|
|
121
|
+
sep = "/"
|
|
122
|
+
kind = parts.shift
|
|
123
|
+
end
|
|
124
|
+
num = parts.first&.match?(/\A\d{1,3}\z/) ? parts.shift : nil
|
|
125
|
+
yr = parts.first&.match?(/\A\d{4}\z/) ? parts.shift : nil
|
|
126
|
+
faces << case kind
|
|
127
|
+
when "amd" then "#{sep}A#{num}#{":#{yr}" if yr}"
|
|
128
|
+
when "cor" then "#{sep}AC#{num}#{":#{yr}" if yr}"
|
|
129
|
+
else " FRAG#{num}"
|
|
130
|
+
end
|
|
72
131
|
end
|
|
73
|
-
|
|
74
|
-
text << (kind == "amd" ? "/A#{iteration}" : "/AC#{iteration}")
|
|
75
|
-
text << ":#{year}" if year
|
|
76
|
-
text
|
|
132
|
+
faces
|
|
77
133
|
end
|
|
134
|
+
|
|
78
135
|
end
|
|
79
136
|
end
|
|
80
137
|
end
|
data/lib/pubid/evs.rb
CHANGED
|
@@ -33,8 +33,19 @@ module Pubid
|
|
|
33
33
|
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
# The trailing language code (" et" / " en" / " et-en") extracts
|
|
37
|
+
# before the grammar runs — the CEN adopted_string is greedy over
|
|
38
|
+
# plain text and would swallow it.
|
|
39
|
+
language = identifier[/ (et-en|et|en)\z/, 1]
|
|
40
|
+
body = language ? identifier.sub(/ #{Regexp.escape(language)}\z/, "") : identifier
|
|
41
|
+
|
|
42
|
+
parsed = Pubid::Parg::Backend.parse(:evs, body)
|
|
43
|
+
built = Builder.new.build(parsed)
|
|
44
|
+
# An "(all parts)" parse wraps the identifier — the language belongs
|
|
45
|
+
# to the inner EVS identifier, not the flavor-less wrapper.
|
|
46
|
+
target = built.is_a?(Pubid::AllPartsIdentifier) ? built.identifiers.first : built
|
|
47
|
+
target.language = language
|
|
48
|
+
built
|
|
38
49
|
end
|
|
39
50
|
|
|
40
51
|
# Build an EVS identifier from a parse-tree hash.
|