pubid 2.0.25 → 2.0.27
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/data/parg/etsi_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/doi_pfs/builder.rb +2 -2
- data/lib/pubid/etsi_pfs/CLAUDE.md +48 -40
- data/lib/pubid/etsi_pfs/builder.rb +45 -12
- data/lib/pubid/etsi_pfs/identifier.rb +23 -41
- data/lib/pubid/etsi_pfs/identifiers/amendment.rb +20 -0
- data/lib/pubid/etsi_pfs/identifiers/corrigendum.rb +20 -0
- data/lib/pubid/etsi_pfs/identifiers/standard.rb +64 -15
- data/lib/pubid/etsi_pfs/identifiers/supplement.rb +72 -0
- data/lib/pubid/etsi_pfs/identifiers.rb +4 -1
- data/lib/pubid/etsi_pfs/pubid_urn_schema.rb +4 -2
- data/lib/pubid/etsi_pfs/renderer.rb +29 -1
- data/lib/pubid/etsi_pfs/urn_generator.rb +40 -3
- data/lib/pubid/etsi_pfs.rb +2 -1
- data/lib/pubid/version.rb +1 -1
- metadata +20 -2
|
@@ -1,12 +1,84 @@
|
|
|
1
1
|
module Pubid
|
|
2
2
|
module CenPfs
|
|
3
|
-
# The official URN generator
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# The official URN generator: the scheme's declared namespace
|
|
4
|
+
# ("cen") and the ordered fused segments
|
|
5
|
+
# (feature 6) — the publisher group, the adopted document's
|
|
6
|
+
# fused MR string, the composed code, the year, the draft
|
|
7
|
+
# stage. A wrapper reads through to its subject and appends
|
|
8
|
+
# its declared chain; the consolidated bundle marks each
|
|
9
|
+
# member with the plus marker.
|
|
6
10
|
class UrnGenerator < ::Pubid::UrnGenerator::Base
|
|
7
11
|
def urn_namespace
|
|
8
12
|
"cen"
|
|
9
13
|
end
|
|
14
|
+
|
|
15
|
+
def generate
|
|
16
|
+
id = @identifier
|
|
17
|
+
members = nil
|
|
18
|
+
frag_segments = []
|
|
19
|
+
wrapper = []
|
|
20
|
+
if id.is_a?(Identifiers::Fragment)
|
|
21
|
+
frag_segments = wrapper_segments(id)
|
|
22
|
+
id = id.base
|
|
23
|
+
end
|
|
24
|
+
if id.is_a?(Identifiers::ConsolidatedIdentifier)
|
|
25
|
+
base, *consolidated = id.identifiers
|
|
26
|
+
members = consolidated
|
|
27
|
+
id = base
|
|
28
|
+
end
|
|
29
|
+
while id.is_a?(Identifiers::Supplement)
|
|
30
|
+
wrapper.prepend(*wrapper_segments(id))
|
|
31
|
+
id = id.base
|
|
32
|
+
end
|
|
33
|
+
@identifier = id
|
|
34
|
+
parts = ["urn", urn_namespace]
|
|
35
|
+
parts.concat(official_segments)
|
|
36
|
+
parts.concat(wrapper)
|
|
37
|
+
members&.each do |member|
|
|
38
|
+
parts << "plus"
|
|
39
|
+
parts.concat(wrapper_segments(member))
|
|
40
|
+
end
|
|
41
|
+
parts.concat(frag_segments)
|
|
42
|
+
parts.join(":")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def official_segments
|
|
48
|
+
segments = []
|
|
49
|
+
group = [@identifier.publisher&.respond_to?(:body) ? @identifier.publisher.body : @identifier.publisher,
|
|
50
|
+
*Array(@identifier.copublishers).map { |p| p.respond_to?(:body) ? p.body : p }].compact.map(&:to_s)
|
|
51
|
+
segments << group.map(&:downcase).join("-") unless group.empty?
|
|
52
|
+
adopted = @identifier.respond_to?(:adopted) ? @identifier.adopted : nil
|
|
53
|
+
mr = adopted&.to_mr_string
|
|
54
|
+
segments << mr.tr("._", "::") unless mr.nil? || mr.empty?
|
|
55
|
+
code = []
|
|
56
|
+
code << @identifier.number.to_s if @identifier.number
|
|
57
|
+
code << "-" + @identifier.part.to_s if @identifier.part
|
|
58
|
+
segments << code.join unless code.empty?
|
|
59
|
+
date = [@identifier.date&.year].compact
|
|
60
|
+
segments << date.join("-") unless date.empty?
|
|
61
|
+
stage = @identifier.typed_stage
|
|
62
|
+
code = stage&.stage_code
|
|
63
|
+
segments << "stage." + code.to_s if code && code.to_s != "published"
|
|
64
|
+
segments
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# One wrapper's declared URN chain: the kind word, then the
|
|
68
|
+
# wrapper's columns (the ordinal, the year-month date).
|
|
69
|
+
def wrapper_segments(id)
|
|
70
|
+
segments = case id
|
|
71
|
+
when Identifiers::Amendment
|
|
72
|
+
["amd", id.number, id.year]
|
|
73
|
+
when Identifiers::Corrigendum
|
|
74
|
+
["cor", id.number, (id.respond_to?(:supplement_date) ? id.supplement_date : [id.year, id.month].compact.join("-") )]
|
|
75
|
+
when Identifiers::Fragment
|
|
76
|
+
["frag", id.number]
|
|
77
|
+
else
|
|
78
|
+
[]
|
|
79
|
+
end
|
|
80
|
+
segments.map(&:to_s).reject(&:empty?)
|
|
81
|
+
end
|
|
10
82
|
end
|
|
11
83
|
end
|
|
12
84
|
end
|
data/lib/pubid/cen_pfs.rb
CHANGED
|
@@ -1,34 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
#
|
|
3
3
|
# PFS-generated flavor — DO NOT EDIT BY HAND.
|
|
4
|
-
# Source: definitions/cen.pfs (pfs:1) sha256:
|
|
4
|
+
# Source: definitions/cen.pfs (pfs:1) sha256:b8f2c00e392dbc03c1fddedad141afa7c930c5430738721b7df0ac69a371d028
|
|
5
5
|
# Compiler: pubid/grammar/pfs 0.1.0 (baked with parsanol 1.3.126)
|
|
6
6
|
# Regenerate: ruby scripts/pfs compile definitions/cen.pfs --as cen_pfs --ruby-out <dir>
|
|
7
7
|
|
|
8
8
|
require "pubid"
|
|
9
9
|
|
|
10
10
|
module Pubid
|
|
11
|
-
# CEN/CENELEC
|
|
11
|
+
# CEN/CENELEC flavor, compiled from its PFS definition
|
|
12
12
|
# (pubid/pubid#706). A compiled flavor is a peer of the coded
|
|
13
13
|
# flavors: same registry, same input guards, same error
|
|
14
14
|
# namespace, same URN contracts.
|
|
15
15
|
module CenPfs
|
|
16
16
|
extend Pubid::PrefixesSupport
|
|
17
17
|
|
|
18
|
-
PREFIXES = ["EN", "
|
|
19
|
-
# The
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
# the base). Carried so consumers and the wizard can
|
|
24
|
-
# introspect what the flavor is composed of.
|
|
25
|
-
MODULES = [
|
|
26
|
-
{ name: "iso.inner", version: 1, identity: { composition: "wrapper-with-adopted", base_module: "iso.inner", adopted_field: :adopted } },
|
|
27
|
-
{ name: "en.overlay", version: 1, identity: { composition: "wrapper-with-adopted", base_module: "iso.inner", adopted_field: :adopted } },
|
|
28
|
-
{ name: "national.prefix", version: 1, identity: { composition: "wrapper-with-adopted", base_module: "iso.inner", adopted_field: :adopted } },
|
|
29
|
-
{ name: "supplements.core", version: 1, identity: { composition: "wrapper-with-adopted", adopted_field: :base } },
|
|
30
|
-
].freeze
|
|
31
|
-
|
|
18
|
+
PREFIXES = ["CEN", "CLC", "CENELEC", "EN", "CWA", "HD", "ES", "CR", "ENV"].freeze
|
|
19
|
+
# The adopted-base dispatch hook (the §4 coded escape hatch):
|
|
20
|
+
# vendored beside the generated classes — a missing hook file
|
|
21
|
+
# fails loudly on the first adoption, never silently.
|
|
22
|
+
autoload :AdoptedBase, "#{__dir__}/cen_pfs/adopted_base"
|
|
32
23
|
autoload :Builder, "#{__dir__}/cen_pfs/builder"
|
|
33
24
|
autoload :Identifier, "#{__dir__}/cen_pfs/identifier"
|
|
34
25
|
autoload :Identifiers, "#{__dir__}/cen_pfs/identifiers"
|
|
@@ -39,57 +39,65 @@ Measured surface (see the spec header for the numbers and their meaning):
|
|
|
39
39
|
(all 15 type tokens; the four number shapes GSM/dotted/complex/simple;
|
|
40
40
|
minor; the parts run; the /A//C supplement runs; `V`-versions and
|
|
41
41
|
`ed.N`; the `(YYYY-MM)` date).
|
|
42
|
-
- **to_hash and to_pubid_urn byte parity:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Amendment
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
42
|
+
- **to_hash and to_pubid_urn byte parity: 24,718/24,718** (phase 3
|
|
43
|
+
closed all three constructs; the former per-construct pins are now
|
|
44
|
+
parity rows):
|
|
45
|
+
1. **collection bindings** (3,772 rows) — `parts` is a `kind: list`
|
|
46
|
+
field binding its `part` item per repetition; the compiled model
|
|
47
|
+
stores the coded `["1","0"]` array, dropped when empty.
|
|
48
|
+
2. **the edition face** (1,833 rows) — the `ed.N` capture folds onto
|
|
49
|
+
`version` + `is_edition: true` (the coded Version component shape).
|
|
50
|
+
3. **supplement wrappers** (168 rows) — the `supplements:` declaration
|
|
51
|
+
folds the printed run into SYNTHESIZED Amendment/Corrigendum classes
|
|
52
|
+
over the base standard, outermost last ("/A1/C1" is Corrigendum over
|
|
53
|
+
Amendment over the standard).
|
|
54
|
+
Trajectory: 18,945 → 22,717 (collections) → 24,718 (fold + wrappers).
|
|
55
|
+
- **to_s** renders the full printed face — the canonical template's
|
|
56
|
+
presence-guarded optionals and the V/ed alternated tail render what
|
|
57
|
+
the input carried (the conditional-render segment feature).
|
|
58
|
+
- **to_urn** is DECLARED (`urn.official`): type lowercased, the composed
|
|
59
|
+
code (number + minor + prefix-glued parts), the v/ed version face
|
|
60
|
+
(the edition affix under the fold flag), the year-month date group —
|
|
61
|
+
byte-identical with the coded generator everywhere the coded one
|
|
62
|
+
renders at all. The CODED generator raises `ArgumentError` on months
|
|
63
|
+
08/09 (`format('%02d', "08")` — the zero-padded string reads as an
|
|
64
|
+
invalid octal); pre-existing on main, pinned by the parity spec — the
|
|
65
|
+
compiled generator renders the captured month verbatim and does not
|
|
66
|
+
share the defect.
|
|
67
|
+
- **to_mr_string** is DECLARED (`mr:`): the coded mr_type /
|
|
68
|
+
mr_number_with_part / mr_year / mr_edition hooks over the columns,
|
|
69
|
+
byte-identical slugs (`etsi.eg.200-053.2004-06.1-5-1`), with the
|
|
70
|
+
wrapper's supplement suffix (`…_a1.1`) carried by the synthesized
|
|
71
|
+
classes.
|
|
69
72
|
|
|
70
73
|
## Non-obvious facts, learned the hard way
|
|
71
74
|
|
|
72
75
|
- The generated `--ruby-out` lands at `lib/etsi_pfs.rb`; the autoload and
|
|
73
76
|
the `#{__dir__}` paths require it at `lib/pubid/etsi_pfs.rb` — move both
|
|
74
77
|
the file and the directory (commands above).
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
future-cutover shape.
|
|
78
|
+
- The instance's URNs resolve through the CODED schema anyway (the
|
|
79
|
+
flavor segment of the canonical tag is `etsi`), so the alias's schema
|
|
80
|
+
rows are the future-cutover shape; the wrapper rows now carry the
|
|
81
|
+
coded `core: [["number", :int]]` ordinals (the phase-3 int-kind
|
|
82
|
+
cores).
|
|
81
83
|
- `V1..1` parses in BOTH flavors (`.` is a version_char) and `GS` is a
|
|
82
84
|
real type token — neither is a parity negative.
|
|
83
85
|
- `Pubid::EtsiPfs.parse("ETSI …")` never hijacks `Pubid.parse` (registry
|
|
84
86
|
keys sort `"etsi"` first), and `Pubid.from_hash` of an alias hash
|
|
85
87
|
resolves the coded `EtsiStandard` (one scheme, one `_type`).
|
|
86
88
|
- The corpus inventory (`spec/fixtures/etsi/identifiers/full/`) is the
|
|
87
|
-
parity source; its bucket split
|
|
88
|
-
edition 1,833 / supplement 168
|
|
89
|
+
parity source; its former bucket split (plain 18,945 / parts 3,772 /
|
|
90
|
+
edition 1,833 / supplement 168) is now the per-gap parity trajectory —
|
|
91
|
+
all 24,718 rows agree on to_hash, to_pubid_urn, to_s, to_urn and
|
|
92
|
+
to_mr_string.
|
|
93
|
+
- The run's internal captures (`supplement_kind` / `supplement_number`)
|
|
94
|
+
bind only through the run's item rows, and unbound captures arrive as
|
|
95
|
+
raw `Parsanol::Slice` leaves — the generated builder unwraps
|
|
96
|
+
`Slice#content` (the cen_cenelec precedent).
|
|
89
97
|
|
|
90
|
-
## v1 boundaries
|
|
98
|
+
## v1 boundaries (still)
|
|
91
99
|
|
|
92
|
-
Single
|
|
93
|
-
`stages
|
|
94
|
-
`pubid-grammar`'s `definitions/README.md` for the language
|
|
95
|
-
the fail-closed validation rules.
|
|
100
|
+
Single declared type per scheme (the `supplements:` wrappers are the one
|
|
101
|
+
multi-class shape), no free-standing `stages:` maps, no semantic decode
|
|
102
|
+
rules — see `pubid-grammar`'s `definitions/README.md` for the language
|
|
103
|
+
reference and the fail-closed validation rules.
|
|
@@ -1,25 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
module Pubid
|
|
2
|
+
module EtsiPfs
|
|
3
|
+
class Builder
|
|
4
|
+
# The wrapper classes by kind face: the run's dispatch.
|
|
5
|
+
SUPPLEMENT_WRAPPERS = {
|
|
6
|
+
"A" => Identifiers::Amendment,
|
|
7
|
+
"C" => Identifiers::Corrigendum,
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
def self.build(parsed_data)
|
|
11
|
+
new.build(parsed_data)
|
|
12
|
+
end
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
def build(data)
|
|
15
|
+
identifier = Identifiers::Standard.new(
|
|
10
16
|
type: data[:type],
|
|
11
17
|
number: data[:number],
|
|
12
18
|
minor: data[:minor],
|
|
13
|
-
parts: data
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
edition: data[:edition],
|
|
19
|
+
parts: build_parts(data),
|
|
20
|
+
version: data[:version] || data[:edition],
|
|
21
|
+
is_edition: !data[:edition].nil?,
|
|
17
22
|
year: data[:year],
|
|
18
23
|
month: data[:month],
|
|
24
|
+
)
|
|
25
|
+
run = Array(data[:supplements])
|
|
26
|
+
run.each do |row|
|
|
27
|
+
item = row[:supplement] || row["supplement"] || {}
|
|
28
|
+
kind = unwrap(item[:supplement_kind] || item["supplement_kind"])
|
|
29
|
+
identifier = SUPPLEMENT_WRAPPERS.fetch(kind).new(
|
|
30
|
+
number: unwrap(item[:supplement_number] || item["supplement_number"]).to_i,
|
|
31
|
+
base: identifier,
|
|
19
32
|
)
|
|
20
33
|
end
|
|
34
|
+
identifier
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The run's internal captures bind only through the item row
|
|
38
|
+
# (the cen_cenelec precedent), so unbound ones arrive as raw
|
|
39
|
+
# leaves — a Parsanol::Slice or a {value:, offset: ...} span.
|
|
40
|
+
# Unwrap the captured value.
|
|
41
|
+
def unwrap(capture)
|
|
42
|
+
return capture.content if capture.is_a?(Parsanol::Slice)
|
|
43
|
+
return capture[:value] if capture.is_a?(Hash) && capture.key?(:value)
|
|
44
|
+
|
|
45
|
+
capture
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The parts column from its bound item rows.
|
|
49
|
+
def build_parts(data)
|
|
50
|
+
Array(data[:parts]).map do |row|
|
|
51
|
+
row[:part] || row["part"]
|
|
52
|
+
end.compact
|
|
21
53
|
end
|
|
22
54
|
end
|
|
23
55
|
end
|
|
24
56
|
|
|
25
57
|
Pubid::EtsiPfs::Builder.prepend(Pubid::Builder::AllPartsWrap)
|
|
58
|
+
end
|
|
@@ -1,50 +1,32 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
attribute :number, :string
|
|
6
|
-
attribute :minor, :string
|
|
7
|
-
attribute :parts, :string
|
|
8
|
-
attribute :supplements, :string
|
|
9
|
-
attribute :version, :string
|
|
10
|
-
attribute :edition, :string
|
|
11
|
-
attribute :year, :string
|
|
12
|
-
attribute :month, :string
|
|
1
|
+
module Pubid
|
|
2
|
+
module EtsiPfs
|
|
3
|
+
class Identifier < ::Pubid::Identifier
|
|
4
|
+
attribute :publisher, :string, default: -> { "ETSI" }
|
|
13
5
|
|
|
14
|
-
|
|
15
|
-
"pubid:etsi:etsi-standard" => "Pubid::EtsiPfs::Identifiers::Standard",
|
|
16
|
-
}.freeze
|
|
6
|
+
PUBLISHER = "ETSI"
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
map "supplements", to: :supplements
|
|
25
|
-
map "version", to: :version
|
|
26
|
-
map "edition", to: :edition
|
|
27
|
-
map "year", to: :year
|
|
28
|
-
map "month", to: :month
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
PUBLISHER = "ETSI"
|
|
8
|
+
# The scheme's declared publisher token: the internal-form
|
|
9
|
+
# cut (to_s(with_publisher: false)) strips it from the
|
|
10
|
+
# rendered face.
|
|
11
|
+
def publisher_prefixes
|
|
12
|
+
[PUBLISHER]
|
|
13
|
+
end
|
|
32
14
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
15
|
+
def self.parse(identifier)
|
|
16
|
+
unless identifier.is_a?(String)
|
|
17
|
+
raise Pubid::Errors::InvalidInputError,
|
|
18
|
+
Pubid::INPUT_NOT_A_STRING_MESSAGE
|
|
19
|
+
end
|
|
38
20
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
21
|
+
if identifier.length > Pubid::MAX_INPUT_LENGTH
|
|
22
|
+
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
23
|
+
end
|
|
42
24
|
|
|
43
|
-
|
|
25
|
+
identifier = ::Pubid.utf8_input(identifier)
|
|
44
26
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
end
|
|
27
|
+
parsed = Pubid::Parg::Model.bound(:etsi_pfs, identifier)
|
|
28
|
+
Builder.build(parsed)
|
|
48
29
|
end
|
|
49
30
|
end
|
|
50
31
|
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Pubid
|
|
2
|
+
module EtsiPfs
|
|
3
|
+
module Identifiers
|
|
4
|
+
# The synthesized Amendment wrapper (PFS supplements
|
|
5
|
+
# declaration): the kind face "A" is the run's
|
|
6
|
+
# dispatch token and the printed notation's face.
|
|
7
|
+
class Amendment < Supplement
|
|
8
|
+
FACE = "A".freeze
|
|
9
|
+
|
|
10
|
+
def self.face
|
|
11
|
+
FACE
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.polymorphic_name
|
|
15
|
+
"pubid:etsi:amendment"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Pubid
|
|
2
|
+
module EtsiPfs
|
|
3
|
+
module Identifiers
|
|
4
|
+
# The synthesized Corrigendum wrapper (PFS supplements
|
|
5
|
+
# declaration): the kind face "C" is the run's
|
|
6
|
+
# dispatch token and the printed notation's face.
|
|
7
|
+
class Corrigendum < Supplement
|
|
8
|
+
FACE = "C".freeze
|
|
9
|
+
|
|
10
|
+
def self.face
|
|
11
|
+
FACE
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.polymorphic_name
|
|
15
|
+
"pubid:etsi:corrigendum"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,22 +1,71 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
module Pubid
|
|
2
|
+
module EtsiPfs
|
|
3
|
+
module Identifiers
|
|
4
|
+
# Concrete ETSI Standard identifier type (PFS type `standard`).
|
|
5
|
+
class Standard < Identifier
|
|
6
6
|
TYPED_STAGES = [].freeze
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
attribute :type, :string
|
|
9
|
+
attribute :number, :string
|
|
10
|
+
attribute :minor, :string
|
|
11
|
+
attribute :parts, :string, collection: true, initialize_empty: true
|
|
12
|
+
attribute :version, :string
|
|
13
|
+
attribute :year, :string
|
|
14
|
+
attribute :month, :string
|
|
15
|
+
attribute :is_edition, :boolean, default: -> { false }
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
key_value do
|
|
18
|
+
map "_type", to: :_type
|
|
19
|
+
map "type", to: :type
|
|
20
|
+
map "number", to: :number
|
|
21
|
+
map "minor", to: :minor
|
|
22
|
+
map "parts", to: :parts
|
|
23
|
+
map "version", to: :version
|
|
24
|
+
map "year", to: :year
|
|
25
|
+
map "month", to: :month
|
|
26
|
+
map "is_edition", to: :is_edition
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.type
|
|
30
|
+
{ key: :etsi, web: :etsi, title: "ETSI Standard", short: "ETSI" }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The scheme-canonical wire tag declared in the definition:
|
|
34
|
+
# to_hash stays byte-identical with the scheme's coded
|
|
35
|
+
# flavor, and this flavor's own from_hash routes the tag
|
|
36
|
+
# to this class.
|
|
37
|
+
def self.polymorphic_name
|
|
38
|
+
"pubid:etsi:etsi-standard"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def mr_type
|
|
42
|
+
mr_sanitize(type)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def mr_number_with_part
|
|
46
|
+
mr_sanitize([number, minor, *Array(parts)].compact.join("-"))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def mr_year
|
|
50
|
+
mr_sanitize([year, month].compact.join("-"))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def mr_edition
|
|
54
|
+
mr_sanitize(version)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The MR charset rule (the coded flavors' mr_sanitize):
|
|
58
|
+
# lowercase, [a-z0-9-], non-charset runs collapsed to one
|
|
59
|
+
# dash; empty answers nil so the joined slug never gains a
|
|
60
|
+
# blank segment.
|
|
61
|
+
def mr_sanitize(value)
|
|
62
|
+
return nil if value.nil? || value.to_s.empty?
|
|
63
|
+
|
|
64
|
+
sanitized = value.to_s.downcase
|
|
65
|
+
.gsub(/[^a-z0-9-]+/, "-").gsub(/\A-+|-+\z/, "")
|
|
66
|
+
sanitized.empty? ? nil : sanitized
|
|
19
67
|
end
|
|
20
68
|
end
|
|
21
69
|
end
|
|
22
70
|
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Pubid
|
|
2
|
+
module EtsiPfs
|
|
3
|
+
module Identifiers
|
|
4
|
+
# The supplement wrapper base (pubid#706 phase 3): the
|
|
5
|
+
# wrapper owns its ordinal and the base identifier; every
|
|
6
|
+
# delegated attribute reads through.
|
|
7
|
+
class Supplement < ::Pubid::EtsiPfs::Identifier
|
|
8
|
+
attribute :base, ::Pubid::EtsiPfs::Identifier
|
|
9
|
+
attribute :number, :integer
|
|
10
|
+
|
|
11
|
+
key_value do
|
|
12
|
+
map "_type", to: :_type
|
|
13
|
+
map "number", to: :number
|
|
14
|
+
map "base", with: { to: :base_to_kv, from: :base_from_kv }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Serialize the base via its own to_hash so it collapses
|
|
18
|
+
# to the leaf standard shape (the coded converters'
|
|
19
|
+
# rule).
|
|
20
|
+
def base_to_kv(model, doc)
|
|
21
|
+
return unless model.base
|
|
22
|
+
|
|
23
|
+
doc.add_child(
|
|
24
|
+
Lutaml::KeyValue::DataModel::Element.new("base", model.base.to_hash),
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Re-dispatch through the flavor base so _type resolves
|
|
29
|
+
# to the concrete Standard (the coded JCGM/ETSI rule).
|
|
30
|
+
def base_from_kv(model, value)
|
|
31
|
+
return unless value
|
|
32
|
+
|
|
33
|
+
model.base = ::Pubid::EtsiPfs::Identifier.from_hash(value)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Delegations: the wrapper reads through to the wrapped
|
|
37
|
+
# standard (the coded SupplementIdentifier shape).
|
|
38
|
+
def type
|
|
39
|
+
base&.type
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def version
|
|
43
|
+
base&.version
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def year
|
|
47
|
+
base&.year
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def month
|
|
51
|
+
base&.month
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def is_edition
|
|
55
|
+
base&.is_edition
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# The printed notation: the kind face + the ordinal.
|
|
59
|
+
def supplement_notation
|
|
60
|
+
"#{self.class.face}#{number}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The MR slug's supplement suffix: the shared MrString
|
|
64
|
+
# recurses into base and appends this (the coded
|
|
65
|
+
# mr_supplement_suffix rule).
|
|
66
|
+
def mr_supplement_suffix
|
|
67
|
+
[supplement_notation.to_s.downcase, number].compact.join(".")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
module Pubid
|
|
2
2
|
module EtsiPfs
|
|
3
3
|
module Identifiers
|
|
4
|
-
|
|
4
|
+
autoload :Standard, "#{__dir__}/identifiers/standard"
|
|
5
|
+
autoload :Supplement, "#{__dir__}/identifiers/supplement"
|
|
6
|
+
autoload :Amendment, "#{__dir__}/identifiers/amendment"
|
|
7
|
+
autoload :Corrigendum, "#{__dir__}/identifiers/corrigendum"
|
|
5
8
|
end
|
|
6
9
|
end
|
|
7
10
|
end
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
atom "year", "year"
|
|
13
13
|
atom "month", "month"
|
|
14
14
|
atom "version", "v"
|
|
15
|
-
type "pubid:
|
|
16
|
-
type "pubid:
|
|
15
|
+
type "pubid:etsi:etsi-standard", "es", core: []
|
|
16
|
+
type "pubid:etsi:amendment", "amd", core: [["number", :int]]
|
|
17
|
+
type "pubid:etsi:corrigendum", "cor", core: [["number", :int]]
|
|
18
|
+
type "pubid:etsi:identifier", "id", core: []
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
end
|