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.
@@ -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
 
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Pubid
4
4
  module Evs
5
- # Builds an EVS national adoption from the composed parse tree: the
6
- # structured :adopted subtree (produced by the embedded CEN grammar) goes
7
- # straight to the CEN/CENELEC builder as data — no string
8
- # re-serialization anywhere.
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
- adopted = Pubid::CenCenelec::Builder.build(data[:adopted])
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
- Identifiers::NationalAdoption.new(
20
- base: adopted,
21
- separator: data[:evs_separator] || "-",
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
@@ -2,19 +2,41 @@
2
2
 
3
3
  module Pubid
4
4
  module Evs
5
- # An EVS national adoption wraps a CEN identifier the same way BSI's
6
- # AdoptedEuropeanNorm does — polymorphic `base` so the
7
- # wrapped object stays a real Pubid::CenCenelec identifier.
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
- # Examples:
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
@@ -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
- # The adopted identifier renders with its own "EN …" prefix, so the
15
- # national prefix composes as "EVS" + separator + adopted.
16
- "EVS#{separator}#{adopted}"
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
- def separator
22
- @id.separator || "-"
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
- def adopted
26
- @id.base.to_s
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
- adopted_urn = identifier.base.to_urn
21
- unless adopted_urn.start_with?(CEN_NAMESPACE)
22
- raise Errors::ParseError,
23
- "expected adopted CEN URN, got #{adopted_urn.inspect}"
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
- adopted_urn.sub(/\A#{Regexp.escape(CEN_NAMESPACE)}/o, EVS_NAMESPACE)
66
+ "#{EVS_NAMESPACE}#{segments}"
27
67
  end
28
68
  end
29
69
  end
@@ -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 adopted
7
- # identifier's human text and delegate to the real grammar so every
8
- # structural check happens in parsers, not in this class.
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
- type = parts.shift
31
- unless /\Aen\z/i.match?(type)
32
- raise Errors::ParseError, "unsupported type in #{urn.inspect}"
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
- text = +"EN"
36
- org_parts = []
37
- org_parts << parts.shift while parts.first&.match?(/\A[a-z][a-z-]*\z/)
38
- if org_parts.any?
39
- org = ORGS.fetch(org_parts.join("-")) do
40
- raise Errors::ParseError, "unknown adopted org in #{urn.inspect}"
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
- text << " #{org}"
43
- end
66
+ number = parts.shift
67
+ raise Errors::ParseError, "missing number in #{urn.inspect}" unless number
44
68
 
45
- number = parts.shift
46
- year = parts.shift
47
- unless number
48
- raise Errors::ParseError,
49
- "missing number in #{urn.inspect}"
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
- text << " #{number}"
53
- text << ":#{year}" if year
96
+ raise Errors::ParseError, "trailing segments in #{urn.inspect}" unless parts.empty?
54
97
 
55
- append_supplement(text, parts, urn) unless parts.empty?
98
+ if na_present
99
+ text << "+NA"
100
+ text << ":#{na_year}" if na_year
101
+ end
102
+ text << " #{language}" if language
56
103
 
57
- adopted = Pubid::CenCenelec.parse(text)
58
- ::Pubid::Evs::Identifiers::NationalAdoption.new(
59
- base: adopted,
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
- # Appends the trailing `amd|cor : iteration [: year]` in human form.
66
- def append_supplement(text, parts, urn)
67
- year = parts.last&.match?(/\A\d{4}\z/) ? parts.pop : nil
68
- kind = parts.shift
69
- iteration = parts.shift
70
- unless %w[amd cor].include?(kind) && iteration && parts.empty?
71
- raise Errors::ParseError, "malformed supplement in #{urn.inspect}"
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
- parsed = Pubid::Parg::Backend.parse(:evs, identifier)
37
- Builder.new.build(parsed)
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.