pubid 2.0.0.pre.alpha.1 → 2.0.0.pre.alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/data/nist/update_codes.yaml +2 -0
- data/lib/pubid/amca/identifier.rb +39 -0
- data/lib/pubid/ansi/identifier.rb +42 -0
- data/lib/pubid/api/identifier.rb +47 -0
- data/lib/pubid/ashrae/identifier.rb +39 -0
- data/lib/pubid/asme/identifier.rb +46 -0
- data/lib/pubid/astm/identifier.rb +77 -0
- data/lib/pubid/bsi/identifier.rb +60 -0
- data/lib/pubid/ccsds/identifier.rb +68 -0
- data/lib/pubid/ccsds/identifiers/base.rb +11 -0
- data/lib/pubid/ccsds/single_identifier.rb +4 -1
- data/lib/pubid/cen_cenelec/identifier.rb +37 -0
- data/lib/pubid/cie/identifier.rb +53 -0
- data/lib/pubid/components/factory.rb +50 -0
- data/lib/pubid/components/typed_stage.rb +4 -0
- data/lib/pubid/components.rb +1 -0
- data/lib/pubid/csa/identifier.rb +56 -0
- data/lib/pubid/etsi/identifier.rb +43 -0
- data/lib/pubid/identifier.rb +8 -1
- data/lib/pubid/idf/identifier.rb +60 -0
- data/lib/pubid/iec/builder.rb +2 -1
- data/lib/pubid/iec/components/code.rb +2 -1
- data/lib/pubid/iec/components/publisher.rb +2 -1
- data/lib/pubid/iec/identifier.rb +235 -0
- data/lib/pubid/iec/identifiers/base.rb +4 -0
- data/lib/pubid/iec/identifiers/consolidated_identifier.rb +0 -4
- data/lib/pubid/iec/identifiers/fragment_identifier.rb +0 -4
- data/lib/pubid/iec/identifiers/sheet_identifier.rb +0 -4
- data/lib/pubid/iec/identifiers/vap_identifier.rb +0 -4
- data/lib/pubid/iec/parser.rb +7 -2
- data/lib/pubid/iec/urn_generator.rb +57 -171
- data/lib/pubid/iec/urn_parser.rb +53 -252
- data/lib/pubid/ieee/identifier.rb +41 -0
- data/lib/pubid/iho/identifier.rb +42 -0
- data/lib/pubid/iho/identifiers/base.rb +1 -1
- data/lib/pubid/iho/identifiers/bibliographic.rb +0 -4
- data/lib/pubid/iho/identifiers/circular_letter.rb +0 -4
- data/lib/pubid/iho/identifiers/miscellaneous.rb +0 -4
- data/lib/pubid/iho/identifiers/publication.rb +0 -4
- data/lib/pubid/iho/identifiers/standard.rb +0 -4
- data/lib/pubid/iho/urn_generator.rb +1 -1
- data/lib/pubid/iso/builder.rb +5 -1
- data/lib/pubid/iso/identifier.rb +261 -0
- data/lib/pubid/iso/parser.rb +4 -2
- data/lib/pubid/iso/scheme.rb +6 -0
- data/lib/pubid/iso/single_identifier.rb +6 -3
- data/lib/pubid/iso/urn_generator.rb +17 -3
- data/lib/pubid/iso/urn_parser.rb +16 -2
- data/lib/pubid/itu/identifier.rb +87 -22
- data/lib/pubid/jcgm/identifier.rb +43 -0
- data/lib/pubid/jis/identifier.rb +43 -0
- data/lib/pubid/nist/builder.rb +174 -5
- data/lib/pubid/nist/components/edition.rb +16 -0
- data/lib/pubid/nist/components/supplement.rb +88 -21
- data/lib/pubid/nist/identifier.rb +62 -0
- data/lib/pubid/nist/identifiers/base.rb +103 -24
- data/lib/pubid/nist/identifiers/circular_supplement.rb +1 -1
- data/lib/pubid/nist/identifiers/crpl_report.rb +1 -4
- data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +10 -0
- data/lib/pubid/nist/identifiers/report.rb +1 -2
- data/lib/pubid/nist/parser.rb +36 -3
- data/lib/pubid/nist/supplement_identifier.rb +8 -24
- data/lib/pubid/nist/urn_generator.rb +14 -8
- data/lib/pubid/nist.rb +1 -0
- data/lib/pubid/oiml/identifier.rb +50 -0
- data/lib/pubid/plateau/identifier.rb +57 -0
- data/lib/pubid/plateau.rb +1 -0
- data/lib/pubid/renderers/base.rb +34 -0
- data/lib/pubid/renderers/directives_renderer.rb +13 -14
- data/lib/pubid/renderers/guide_renderer.rb +5 -1
- data/lib/pubid/renderers/human_readable.rb +20 -8
- data/lib/pubid/renderers/iwa_renderer.rb +5 -1
- data/lib/pubid/renderers/supplement_renderer.rb +4 -1
- data/lib/pubid/rendering/context.rb +5 -2
- data/lib/pubid/sae/identifier.rb +23 -0
- data/lib/pubid/scheme.rb +12 -0
- data/lib/pubid/version.rb +1 -1
- metadata +5 -2
|
@@ -2,191 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Iec
|
|
5
|
+
# Generates IEC URNs in the legacy positional format used as ground truth
|
|
6
|
+
# by relaton-data-iec:
|
|
7
|
+
#
|
|
8
|
+
# urn:iec:std:{publisher}:{number}[-{part}]:{date}:{type}:{deliverable}:{language}[:{adjuncts}]
|
|
9
|
+
#
|
|
10
|
+
# e.g. +urn:iec:std:iec:60050-102:2007:::+ (type after date; trailing empty
|
|
11
|
+
# type/deliverable/language slots). This is a port of relaton-iec's
|
|
12
|
+
# +Relaton::Iec.code_to_urn+ operating on +identifier.to_s+, which
|
|
13
|
+
# round-trips faithfully. The all-parts series URN is the one exception: it
|
|
14
|
+
# omits the language slot (8 fields), so it has a dedicated branch.
|
|
5
15
|
class UrnGenerator < Pubid::UrnGenerator::Base
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
when Identifiers::VapIdentifier
|
|
9
|
-
generate_vap_urn
|
|
10
|
-
when Identifiers::FragmentIdentifier
|
|
11
|
-
generate_fragment_urn
|
|
12
|
-
when Identifiers::SheetIdentifier
|
|
13
|
-
generate_sheet_urn
|
|
14
|
-
when SupplementIdentifier
|
|
15
|
-
generate_supplement_urn
|
|
16
|
-
else
|
|
17
|
-
generate_base_urn
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
def generate_vap_urn
|
|
24
|
-
base_gen = self.class.new(identifier.base_identifier)
|
|
25
|
-
base_urn = base_gen.generate
|
|
26
|
-
|
|
27
|
-
base_part = base_urn.sub(/^urn:iec:std:/, "")
|
|
28
|
-
|
|
29
|
-
parts = ["urn", "iec", "std", base_part]
|
|
30
|
-
|
|
31
|
-
if identifier.vap_suffix
|
|
32
|
-
suffix = identifier.vap_suffix.to_s
|
|
33
|
-
parts << "vap.#{suffix.downcase}"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
if identifier.edition&.number
|
|
37
|
-
parts << "ed.#{identifier.edition.number}"
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
parts.join(":")
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def generate_fragment_urn
|
|
44
|
-
base_gen = self.class.new(identifier.base_identifier)
|
|
45
|
-
base_urn = base_gen.generate
|
|
46
|
-
|
|
47
|
-
base_part = base_urn.sub(/^urn:iec:std:/, "")
|
|
48
|
-
|
|
49
|
-
parts = ["urn", "iec", "std", base_part]
|
|
50
|
-
|
|
51
|
-
if identifier.fragment_number
|
|
52
|
-
frag_type = identifier.base_identifier.is_a?(Identifiers::Corrigendum) ? "fragc" : "frag"
|
|
53
|
-
parts << "#{frag_type}.#{identifier.fragment_number}"
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
if identifier.edition&.number
|
|
57
|
-
parts << "ed.#{identifier.edition.number}"
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
parts.join(":")
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def generate_sheet_urn
|
|
64
|
-
base_gen = self.class.new(identifier.base_identifier)
|
|
65
|
-
base_urn = base_gen.generate
|
|
66
|
-
|
|
67
|
-
base_part = base_urn.sub(/^urn:iec:std:/, "")
|
|
68
|
-
|
|
69
|
-
parts = ["urn", "iec", "std", base_part]
|
|
70
|
-
|
|
71
|
-
if identifier.sheet_number
|
|
72
|
-
parts << "sheet.#{identifier.sheet_number}"
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
if identifier.year
|
|
76
|
-
parts << identifier.year
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
parts.join(":")
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def generate_base_urn
|
|
83
|
-
parts = ["urn", "iec", "std"]
|
|
16
|
+
# Deliverable markers occupying the positional deliverable slot.
|
|
17
|
+
DELIVERABLES = /cmv|csv|exv|prv|rlv|ser/.freeze
|
|
84
18
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
type_comp = type_component
|
|
88
|
-
parts << type_comp if type_comp
|
|
89
|
-
|
|
90
|
-
docnumber = docnumber_component
|
|
91
|
-
parts << docnumber if docnumber
|
|
92
|
-
|
|
93
|
-
date_str = urn_date_string(identifier.date)
|
|
94
|
-
parts << date_str if date_str
|
|
95
|
-
|
|
96
|
-
if identifier.stage_iteration
|
|
97
|
-
parts << "iter.#{identifier.stage_iteration}"
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
if identifier.edition&.number
|
|
101
|
-
parts << "ed.#{identifier.edition.number}"
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
if identifier.languages&.any?
|
|
105
|
-
lang_codes = identifier.languages.map(&:code).join(",")
|
|
106
|
-
parts << lang_codes
|
|
107
|
-
end
|
|
19
|
+
def generate
|
|
20
|
+
return series_urn if identifier.respond_to?(:all_parts) && identifier.all_parts
|
|
108
21
|
|
|
109
|
-
|
|
22
|
+
code_to_urn(identifier.to_s, urn_language)
|
|
110
23
|
end
|
|
111
24
|
|
|
112
|
-
|
|
113
|
-
current = identifier
|
|
114
|
-
supplement_chain = []
|
|
115
|
-
|
|
116
|
-
while current.is_a?(SupplementIdentifier)
|
|
117
|
-
supplement_chain.unshift(current)
|
|
118
|
-
current = current.base_identifier
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
base_id = current
|
|
122
|
-
|
|
123
|
-
parts = ["urn", "iec", "std"]
|
|
124
|
-
|
|
125
|
-
if base_id
|
|
126
|
-
parts << publisher_component(base_id)
|
|
127
|
-
|
|
128
|
-
type_comp = type_component(base_id)
|
|
129
|
-
parts << type_comp if type_comp
|
|
130
|
-
|
|
131
|
-
docnumber = docnumber_component(base_id)
|
|
132
|
-
parts << docnumber if docnumber
|
|
133
|
-
|
|
134
|
-
base_date_str = urn_date_string(base_id.date)
|
|
135
|
-
parts << base_date_str if base_date_str
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
supplement_chain.each do |supp|
|
|
139
|
-
suppl_type = supp.typed_stage&.type_code&.to_s
|
|
140
|
-
parts << suppl_type if suppl_type
|
|
141
|
-
|
|
142
|
-
supp_date_str = urn_date_string(supp.date)
|
|
143
|
-
parts << supp_date_str if supp_date_str
|
|
144
|
-
|
|
145
|
-
if supp.number
|
|
146
|
-
parts << "v#{supp.number}"
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
if supp.stage_iteration
|
|
150
|
-
parts << "iter.#{supp.stage_iteration}"
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
parts.join(":")
|
|
155
|
-
end
|
|
25
|
+
private
|
|
156
26
|
|
|
157
|
-
|
|
158
|
-
|
|
27
|
+
# Hyphen-joined language codes (e.g. "en-fr"), or nil when unset.
|
|
28
|
+
def urn_language
|
|
29
|
+
return nil unless identifier.respond_to?(:languages)
|
|
159
30
|
|
|
160
|
-
|
|
31
|
+
langs = identifier.languages
|
|
32
|
+
return nil unless langs&.any?
|
|
161
33
|
|
|
162
|
-
|
|
163
|
-
publishers.map(&:to_s).map(&:downcase).join("-")
|
|
34
|
+
langs.map(&:code).join("-")
|
|
164
35
|
end
|
|
165
36
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
37
|
+
# The all-parts series URN drops the language slot and carries no part or
|
|
38
|
+
# date: urn:iec:std:iec:80000:::ser.
|
|
39
|
+
def series_urn
|
|
40
|
+
code = identifier.to_s.sub(/\s*\(all parts\)\s*\z/, "")
|
|
41
|
+
m = code.downcase.match(/(?<head>\S+)\s+(?<pnum>[\d-]+)/)
|
|
42
|
+
head = m[:head].split("/").join("-")
|
|
43
|
+
["urn", "iec", "std", head, m[:pnum], "", "", "ser"].join(":")
|
|
173
44
|
end
|
|
174
45
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
46
|
+
# Port of Relaton::Iec.code_to_urn.
|
|
47
|
+
def code_to_urn(code, lang = nil)
|
|
48
|
+
rest = code.downcase.sub(%r{
|
|
49
|
+
(?<head>[^\s]+)\s
|
|
50
|
+
(?<type>is|ts|tr|pas|srd|guide|tec|wp)?(?(<type>)\s)
|
|
51
|
+
(?<pnum>[\d-]+)\s?
|
|
52
|
+
(?<_dd>:)?(?(<_dd>)(?<date>[\d-]+)\s?)
|
|
53
|
+
}x, "")
|
|
54
|
+
m = $~
|
|
55
|
+
return unless m && m[:head] && m[:pnum]
|
|
56
|
+
|
|
57
|
+
deliv = DELIVERABLES.match(code.downcase).to_s
|
|
58
|
+
urn = ["urn", "iec", "std", m[:head].split("/").join("-"), m[:pnum],
|
|
59
|
+
m[:date], m[:type], deliv, lang]
|
|
60
|
+
(urn + adjunct_to_urn(rest)).join(":")
|
|
182
61
|
end
|
|
183
62
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
63
|
+
# Port of Relaton::Iec.ajunct_to_urn — recursively emits amd/cor/ish
|
|
64
|
+
# adjuncts, prefixing "plus" for the "+" (consolidated) relation.
|
|
65
|
+
def adjunct_to_urn(rest)
|
|
66
|
+
r = rest.sub(%r{
|
|
67
|
+
(?<pl>\+|/)(?(<pl>)(?<adjunct>(?:amd|cor|ish))(?<adjnum>\d+)\s?)
|
|
68
|
+
(?<_d2>:)?(?(<_d2>)(?<adjdt>[\d-]+)\s?)
|
|
69
|
+
}x, "")
|
|
70
|
+
m = $~ || {}
|
|
71
|
+
return [] unless m[:adjunct]
|
|
72
|
+
|
|
73
|
+
plus = "plus" if m[:pl] == "+"
|
|
74
|
+
urn = [plus, m[:adjunct], m[:adjnum], m[:adjdt]]
|
|
75
|
+
urn + adjunct_to_urn(r)
|
|
190
76
|
end
|
|
191
77
|
end
|
|
192
78
|
end
|
data/lib/pubid/iec/urn_parser.rb
CHANGED
|
@@ -2,55 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Iec
|
|
5
|
-
# Parses
|
|
5
|
+
# Parses IEC URNs in the legacy positional format (relaton-data-iec ground
|
|
6
|
+
# truth):
|
|
6
7
|
#
|
|
7
|
-
#
|
|
8
|
+
# urn:iec:std:{publisher}:{number}[-{part}]:{date}:{type}:{deliverable}:{language}[:{adjuncts}]
|
|
8
9
|
#
|
|
9
10
|
# Examples:
|
|
10
|
-
# - urn:iec:std:iec:60050:2011
|
|
11
|
-
# - urn:iec:std:iec:
|
|
12
|
-
# - urn:iec:std:iec:60050-
|
|
13
|
-
# - urn:iec:std:iec:
|
|
11
|
+
# - urn:iec:std:iec:60050:2011:::
|
|
12
|
+
# - urn:iec:std:iec:62547:2013:tr:: (type after date)
|
|
13
|
+
# - urn:iec:std:iec:60050-102:2007:::::amd:1:2017
|
|
14
|
+
# - urn:iec:std:iec:60034-16-3:1996:ts::fr (deliverable empty, language fr)
|
|
15
|
+
# - urn:iec:std:iec:80000:::ser (all-parts series)
|
|
16
|
+
#
|
|
17
|
+
# This is a port of relaton-iec's +urn_to_code+: the positional fields are
|
|
18
|
+
# reassembled into a code string which is then run through the text parser
|
|
19
|
+
# (+Identifier.parse+), so there is a single source of truth for building
|
|
20
|
+
# the identifier object.
|
|
14
21
|
class UrnParser
|
|
15
|
-
# Reverse mappings from URN format to PubID components
|
|
16
|
-
TYPED_STAGE_REVERSE_MAP = {
|
|
17
|
-
"WD" => :wd,
|
|
18
|
-
"WDS" => :wds,
|
|
19
|
-
"CD" => :cd,
|
|
20
|
-
"CDV" => :cdv,
|
|
21
|
-
"DIS" => :dis,
|
|
22
|
-
"FDIS" => :fdis,
|
|
23
|
-
"PDAM" => :pdam,
|
|
24
|
-
"DAM" => :dam,
|
|
25
|
-
"FDAM" => :fdamd,
|
|
26
|
-
"DCOR" => :dcor,
|
|
27
|
-
"FDCOR" => :fdcor,
|
|
28
|
-
"CDTS" => :cdts,
|
|
29
|
-
"DTS" => :dts,
|
|
30
|
-
"FDTS" => :fdts,
|
|
31
|
-
"PRF" => :prf,
|
|
32
|
-
"PWI" => :pwi,
|
|
33
|
-
"NP" => :np,
|
|
34
|
-
"AWI" => :awi,
|
|
35
|
-
"NWIP" => :nwip,
|
|
36
|
-
}.freeze
|
|
37
|
-
|
|
38
|
-
SUPPLEMENT_TYPE_MAP = {
|
|
39
|
-
"amd" => :amd,
|
|
40
|
-
"cor" => :cor,
|
|
41
|
-
}.freeze
|
|
42
|
-
|
|
43
|
-
TYPE_CODE_REVERSE_MAP = {
|
|
44
|
-
"tr" => :tr,
|
|
45
|
-
"ts" => :ts,
|
|
46
|
-
"pas" => :pas,
|
|
47
|
-
"guide" => :guide,
|
|
48
|
-
"isp" => :isp,
|
|
49
|
-
"r" => :r,
|
|
50
|
-
"sr" => :sr,
|
|
51
|
-
"tap" => :tap,
|
|
52
|
-
}.freeze
|
|
53
|
-
|
|
54
22
|
# Parse IEC URN string
|
|
55
23
|
# @param urn [String] URN string to parse
|
|
56
24
|
# @return [Identifier] parsed identifier
|
|
@@ -62,227 +30,60 @@ module Pubid
|
|
|
62
30
|
# @param urn [String] URN string
|
|
63
31
|
# @return [Identifier] parsed identifier
|
|
64
32
|
def parse_urn(urn)
|
|
65
|
-
# Remove urn:iec:std: prefix
|
|
66
33
|
unless urn.start_with?("urn:iec:std:")
|
|
67
|
-
raise Errors::ParseError,
|
|
68
|
-
"Invalid IEC URN: #{urn}"
|
|
34
|
+
raise Errors::ParseError, "Invalid IEC URN: #{urn}"
|
|
69
35
|
end
|
|
70
36
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# Parse publisher(s) - first part
|
|
74
|
-
publishers = parse_publisher(parts.shift)
|
|
75
|
-
|
|
76
|
-
# Parse type - optional (defaults to IS)
|
|
77
|
-
type_code = nil
|
|
78
|
-
type_code = parse_type(parts.first) if parts.first && TYPE_CODE_REVERSE_MAP.key?(parts.first.downcase)
|
|
79
|
-
parts.shift if type_code
|
|
80
|
-
|
|
81
|
-
# Parse number
|
|
82
|
-
number_part = parts.shift
|
|
83
|
-
number, part, subpart = parse_number_part(number_part)
|
|
84
|
-
|
|
85
|
-
# Check for stage (stage-XX.XX or typed stage like WD, CD, etc.)
|
|
86
|
-
stage_code = nil
|
|
87
|
-
stage_iteration = nil
|
|
88
|
-
if parts.first&.start_with?("stage-")
|
|
89
|
-
stage_code, stage_iteration = parse_stage_code(parts.shift)
|
|
90
|
-
elsif TYPED_STAGE_REVERSE_MAP.key?(parts.first&.upcase)
|
|
91
|
-
stage_abbr = parts.shift.upcase
|
|
92
|
-
stage_code = TYPED_STAGE_REVERSE_MAP[stage_abbr]
|
|
93
|
-
# Check for iteration (WD.2 format)
|
|
94
|
-
if stage_abbr.include?(".")
|
|
95
|
-
stage_code, stage_iteration = stage_abbr.split(".")
|
|
96
|
-
stage_code = TYPED_STAGE_REVERSE_MAP[stage_code]
|
|
97
|
-
stage_iteration = stage_iteration.to_i
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Parse date if present (year or year-month)
|
|
102
|
-
date = nil
|
|
103
|
-
if parts.first&.match(/^\d{4}(-\d{2})?$/)
|
|
104
|
-
date = parts.shift
|
|
105
|
-
end
|
|
37
|
+
code, lang, all_parts = urn_to_code(urn)
|
|
38
|
+
raise Errors::ParseError, "Invalid IEC URN: #{urn}" unless code
|
|
106
39
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if
|
|
110
|
-
|
|
40
|
+
id = Pubid::Iec::Identifier.parse(code)
|
|
41
|
+
id.all_parts = true if all_parts && id.respond_to?(:all_parts=)
|
|
42
|
+
if lang && !lang.empty? && id.respond_to?(:languages=)
|
|
43
|
+
id.languages = [::Pubid::Components::Language.new(code: lang)]
|
|
111
44
|
end
|
|
112
|
-
|
|
113
|
-
# Check for supplements (amd, cor)
|
|
114
|
-
supplements = []
|
|
115
|
-
while parts.any?
|
|
116
|
-
supp_type = nil
|
|
117
|
-
supp_number = nil
|
|
118
|
-
supp_date = nil
|
|
119
|
-
supp_stage = nil
|
|
120
|
-
|
|
121
|
-
# Check for supplement stage
|
|
122
|
-
if parts.first&.start_with?("stage-")
|
|
123
|
-
supp_stage_data = parts.shift
|
|
124
|
-
supp_stage, = parse_stage_code(supp_stage_data)
|
|
125
|
-
elsif TYPED_STAGE_REVERSE_MAP.key?(parts.first&.upcase)
|
|
126
|
-
supp_stage_abbr = parts.shift.upcase
|
|
127
|
-
supp_stage = TYPED_STAGE_REVERSE_MAP[supp_stage_abbr]
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Check for supplement type (amd, cor)
|
|
131
|
-
if SUPPLEMENT_TYPE_MAP.key?(parts.first&.downcase)
|
|
132
|
-
supp_type = SUPPLEMENT_TYPE_MAP[parts.shift.downcase]
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
# Check for version (v1, v2, etc.) or number
|
|
136
|
-
if parts.first&.start_with?("v")
|
|
137
|
-
version_str = parts.shift
|
|
138
|
-
supp_number = version_str.sub("v", "").to_i
|
|
139
|
-
elsif parts.first&.match(/^\d+$/)
|
|
140
|
-
# Could be year or supplement number
|
|
141
|
-
if parts.first&.match(/^\d{4}$/)
|
|
142
|
-
# 4 digits = year
|
|
143
|
-
supp_date = parts.shift
|
|
144
|
-
else
|
|
145
|
-
# 1-3 digits = supplement number
|
|
146
|
-
supp_number = parts.shift.to_i
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Next part might be year if not already set
|
|
151
|
-
if supp_date.nil? && parts.first&.match(/^\d{4}(-\d{2})?$/)
|
|
152
|
-
supp_date = parts.shift
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Safety: consume unrecognized token to prevent infinite loop
|
|
156
|
-
unless supp_type || supp_number || supp_date || supp_stage
|
|
157
|
-
parts.shift
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
supplements << {
|
|
161
|
-
type: supp_type,
|
|
162
|
-
number: supp_number,
|
|
163
|
-
date: supp_date,
|
|
164
|
-
stage: supp_stage,
|
|
165
|
-
}
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# Build the identifier hash
|
|
169
|
-
build_identifier(publishers, number, part, subpart, type_code, stage_code, stage_iteration,
|
|
170
|
-
date, edition, supplements)
|
|
45
|
+
id
|
|
171
46
|
end
|
|
172
47
|
|
|
173
48
|
private
|
|
174
49
|
|
|
175
|
-
#
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
50
|
+
# Port of Relaton::Iec.urn_to_code. Reassembles the positional URN fields
|
|
51
|
+
# into a text code string. Returns [code, language, all_parts].
|
|
52
|
+
def urn_to_code(urn)
|
|
53
|
+
fields = urn.upcase.split(":")
|
|
54
|
+
return if fields.size < 5
|
|
55
|
+
|
|
56
|
+
head, num, date, type, deliv, lang = fields[3, 8]
|
|
57
|
+
all_parts = false
|
|
58
|
+
|
|
59
|
+
code = head.gsub("-", "/")
|
|
60
|
+
code += " #{type}" unless type.nil? || type.empty?
|
|
61
|
+
code += " #{num}"
|
|
62
|
+
code += ":#{date}" unless date.nil? || date.empty?
|
|
63
|
+
code += adjunct_to_code(fields[9..])
|
|
64
|
+
|
|
65
|
+
# "ser" marks an all-parts series rather than a deliverable suffix; it
|
|
66
|
+
# is signalled out-of-band (the code built from a series URN carries no
|
|
67
|
+
# part or date, so to_s renders "IEC NNNN (all parts)").
|
|
68
|
+
if deliv && deliv.casecmp("SER").zero?
|
|
69
|
+
all_parts = true
|
|
70
|
+
elsif deliv && !deliv.empty?
|
|
71
|
+
code += " #{deliv}"
|
|
197
72
|
end
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
# Parse stage code (stage-XX.XX format)
|
|
201
|
-
def parse_stage_code(stage_str)
|
|
202
|
-
stage_code = stage_str.sub("stage-", "")
|
|
203
73
|
|
|
204
|
-
|
|
205
|
-
stage_code, iteration = stage_code.split(".")
|
|
206
|
-
[stage_code.to_sym, iteration.to_i]
|
|
207
|
-
else
|
|
208
|
-
[stage_code.to_sym, nil]
|
|
209
|
-
end
|
|
74
|
+
[code, lang&.downcase, all_parts]
|
|
210
75
|
end
|
|
211
76
|
|
|
212
|
-
#
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
publisher: publishers.first,
|
|
218
|
-
copublishers: publishers[1..]&.map { |c| { copublisher: c } },
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
# Build number_with_part (expected by Builder)
|
|
222
|
-
if part || subpart
|
|
223
|
-
number_with_part = number
|
|
224
|
-
number_with_part += "-#{part}" if part
|
|
225
|
-
number_with_part += "-#{subpart}" if subpart
|
|
226
|
-
base_hash[:number_with_part] = number_with_part
|
|
227
|
-
else
|
|
228
|
-
base_hash[:number] = number
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
base_hash[:date] = date if date
|
|
232
|
-
base_hash[:edition] = edition if edition
|
|
233
|
-
|
|
234
|
-
# Add type_with_stage if type_code is present
|
|
235
|
-
if type_code && type_code != :is
|
|
236
|
-
base_hash[:type_with_stage] = type_code.to_s.upcase
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# Add stage if present
|
|
240
|
-
if stage_code
|
|
241
|
-
# Look up the typed stage abbreviation from stage_code
|
|
242
|
-
typed_stage = Scheme.locate_typed_stage_by_stage_code(stage_code)
|
|
243
|
-
if typed_stage
|
|
244
|
-
base_hash[:type_with_stage] =
|
|
245
|
-
typed_stage.abbr.is_a?(Array) ? typed_stage.abbr.first : typed_stage.abbr
|
|
246
|
-
else
|
|
247
|
-
# Fallback to harmonized stage notation
|
|
248
|
-
base_hash[:stage] = stage_code.to_s.gsub(".", ".")
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
# Build supplements recursively
|
|
253
|
-
supplements.reverse_each do |supp|
|
|
254
|
-
supp_hash = {}
|
|
255
|
-
|
|
256
|
-
if supp[:stage]
|
|
257
|
-
typed_stage = Scheme.locate_typed_stage_by_stage_code(supp[:stage])
|
|
258
|
-
supp_hash[:type_with_stage] = if
|
|
259
|
-
typed_stage
|
|
260
|
-
typed_stage.abbr.is_a?(Array) ? typed_stage.abbr.first : typed_stage.abbr
|
|
261
|
-
else
|
|
262
|
-
supp[:stage].to_s.upcase
|
|
263
|
-
end
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
supp_hash[:type_with_stage] ||= supp[:type].to_s.upcase if supp[:type]
|
|
267
|
-
|
|
268
|
-
if supp[:number]
|
|
269
|
-
supp_hash[:number] = supp[:number].to_s
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
if supp[:date]
|
|
273
|
-
supp_hash[:date] = supp[:date].to_s
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# Wrap current identifier with supplement
|
|
277
|
-
base_hash = {
|
|
278
|
-
base_identifier: base_hash,
|
|
279
|
-
**supp_hash,
|
|
280
|
-
}
|
|
281
|
-
end
|
|
77
|
+
# Port of Relaton::Iec.ajanct_to_code — recursively rebuilds amd/cor/ish
|
|
78
|
+
# adjuncts from (relation, type, number, date) quartets. A "PLUS"
|
|
79
|
+
# relation token (the consolidated "+" marker) yields "+"; otherwise "/".
|
|
80
|
+
def adjunct_to_code(fields)
|
|
81
|
+
return "" if fields.nil? || fields.empty?
|
|
282
82
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
83
|
+
rel, type, num, date = fields[0..3]
|
|
84
|
+
code = (rel.empty? ? "/" : "+") + type + num
|
|
85
|
+
code += ":#{date}" unless date.nil? || date.empty?
|
|
86
|
+
code + adjunct_to_code(fields[4..])
|
|
286
87
|
end
|
|
287
88
|
end
|
|
288
89
|
end
|
|
@@ -7,6 +7,47 @@ module Pubid
|
|
|
7
7
|
def parse(input)
|
|
8
8
|
Identifiers::Base.parse(input)
|
|
9
9
|
end
|
|
10
|
+
|
|
11
|
+
# Factory mirroring pubid 1.x's `Pubid::Ieee::Identifier.create` API.
|
|
12
|
+
# Dispatches via {Pubid::Ieee::Scheme.locate_identifier_klass_by_type_code}.
|
|
13
|
+
# Default subclass (no `type:`) is Identifiers::Standard — the
|
|
14
|
+
# canonical class produced by parsing typical IEEE identifiers.
|
|
15
|
+
def create(type: nil, **opts)
|
|
16
|
+
klass = type ?
|
|
17
|
+
Scheme.locate_identifier_klass_by_type_code(type) :
|
|
18
|
+
Identifiers::Standard
|
|
19
|
+
attrs = coerce_create_attrs(opts)
|
|
20
|
+
# ProjectDraftIdentifier renders the "P" prefix from typed_stage,
|
|
21
|
+
# not from `type`. Set both so the output matches a parsed form.
|
|
22
|
+
if klass == Identifiers::ProjectDraftIdentifier
|
|
23
|
+
attrs[:type] = "P"
|
|
24
|
+
attrs[:typed_stage] ||= Scheme.locate_typed_stage_by_abbr("P")
|
|
25
|
+
end
|
|
26
|
+
klass.new(**attrs)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def coerce_create_attrs(opts)
|
|
32
|
+
attrs = {}
|
|
33
|
+
attrs[:publisher] = opts[:publisher].to_s if opts[:publisher]
|
|
34
|
+
if opts[:copublisher]
|
|
35
|
+
attrs[:copublisher] =
|
|
36
|
+
Array(opts[:copublisher]).map(&:to_s)
|
|
37
|
+
end
|
|
38
|
+
# :code or :number alias
|
|
39
|
+
if (v = opts[:code] || opts[:number])
|
|
40
|
+
attrs[:code] = v.to_s
|
|
41
|
+
end
|
|
42
|
+
%i[year edition month day draft_status].each do |k|
|
|
43
|
+
v = opts[k]
|
|
44
|
+
attrs[k] = v.to_s unless v.nil?
|
|
45
|
+
end
|
|
46
|
+
attrs[:redline] = opts[:redline] if opts.key?(:redline)
|
|
47
|
+
# TODO(create-shim): amendments/corrigenda/revision_of/incorporates
|
|
48
|
+
# are nested Base relations; not yet wired through.
|
|
49
|
+
attrs
|
|
50
|
+
end
|
|
10
51
|
end
|
|
11
52
|
end
|
|
12
53
|
end
|
data/lib/pubid/iho/identifier.rb
CHANGED
|
@@ -14,6 +14,48 @@ module Pubid
|
|
|
14
14
|
rescue Parslet::ParseFailed => e
|
|
15
15
|
raise "Failed to parse IHO identifier '#{identifier}': #{e.message}"
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
# Factory that builds an IHO identifier from a hash of primitives.
|
|
19
|
+
#
|
|
20
|
+
# IHO identifiers are simpler than ISO/IEC: attributes are plain
|
|
21
|
+
# strings (no Component wrapping), so coercion just calls #to_s.
|
|
22
|
+
#
|
|
23
|
+
# Dispatch:
|
|
24
|
+
# * `type:` accepts the type key (`:standard`, `:publication`,
|
|
25
|
+
# `:miscellaneous`, `:bibliographic`, `:circular_letter`) or
|
|
26
|
+
# the IHO series letter (`"S"`, `"P"`, `"M"`, `"B"`, `"C"`).
|
|
27
|
+
# * Default is {Identifiers::Standard}.
|
|
28
|
+
#
|
|
29
|
+
# @param type [Symbol, String, nil]
|
|
30
|
+
# @param opts [Hash] :publisher (default "IHO"), :code, :appendix,
|
|
31
|
+
# :part, :annex, :supplement, :version
|
|
32
|
+
def self.create(type: nil, **opts)
|
|
33
|
+
klass = resolve_create_class(type)
|
|
34
|
+
klass.new(**coerce_create_attrs(opts))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.resolve_create_class(type)
|
|
38
|
+
return Identifiers::Standard if type.nil?
|
|
39
|
+
|
|
40
|
+
by_key = Scheme::IDENTIFIERS.to_h { |k| [k.type[:key], k] }
|
|
41
|
+
return by_key[type.to_sym] if by_key.key?(type.to_sym)
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
Scheme.identifier_klass_for_type_letter(type.to_s.upcase)
|
|
45
|
+
rescue KeyError
|
|
46
|
+
raise ArgumentError, "Unknown IHO type: #{type.inspect}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.coerce_create_attrs(opts)
|
|
51
|
+
attrs = {}
|
|
52
|
+
%i[publisher code appendix part annex supplement version].each do |k|
|
|
53
|
+
v = opts[k]
|
|
54
|
+
attrs[k] = v.to_s unless v.nil?
|
|
55
|
+
end
|
|
56
|
+
attrs
|
|
57
|
+
end
|
|
58
|
+
private_class_method :resolve_create_class, :coerce_create_attrs
|
|
17
59
|
end
|
|
18
60
|
end
|
|
19
61
|
end
|