pubid 2.0.0.pre.alpha.6 → 2.0.0.pre.alpha.7
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 +40 -0
- data/lib/pubid/amca.rb +6 -0
- data/lib/pubid/ansi.rb +7 -0
- data/lib/pubid/api.rb +5 -0
- data/lib/pubid/ashrae.rb +6 -0
- data/lib/pubid/asme.rb +5 -0
- data/lib/pubid/astm.rb +5 -0
- data/lib/pubid/bsi.rb +15 -0
- data/lib/pubid/ccsds.rb +5 -0
- data/lib/pubid/cen_cenelec.rb +7 -0
- data/lib/pubid/cie.rb +5 -0
- data/lib/pubid/csa.rb +5 -0
- data/lib/pubid/etsi.rb +7 -0
- data/lib/pubid/iala/builder.rb +62 -6
- data/lib/pubid/iala/identifier.rb +11 -7
- data/lib/pubid/iala/identifiers/advice.rb +15 -0
- data/lib/pubid/iala/identifiers/annex.rb +55 -0
- data/lib/pubid/iala/identifiers/general_assembly.rb +16 -0
- data/lib/pubid/iala/identifiers/letter.rb +16 -0
- data/lib/pubid/iala/identifiers.rb +12 -8
- data/lib/pubid/iala/parser.rb +39 -8
- data/lib/pubid/iala/renderer.rb +14 -0
- data/lib/pubid/iala/urn_generator.rb +17 -0
- data/lib/pubid/iala.rb +5 -0
- data/lib/pubid/idf.rb +5 -0
- data/lib/pubid/iec.rb +8 -0
- data/lib/pubid/ieee.rb +8 -0
- data/lib/pubid/iho.rb +5 -0
- data/lib/pubid/iso.rb +6 -0
- data/lib/pubid/itu.rb +5 -0
- data/lib/pubid/jcgm.rb +5 -0
- data/lib/pubid/jis.rb +6 -0
- data/lib/pubid/nist.rb +13 -0
- data/lib/pubid/oiml/builder.rb +33 -0
- data/lib/pubid/oiml/identifier.rb +1 -0
- data/lib/pubid/oiml/identifiers/bulletin.rb +101 -0
- data/lib/pubid/oiml/identifiers.rb +1 -0
- data/lib/pubid/oiml/parser.rb +43 -1
- data/lib/pubid/oiml/renderer.rb +51 -0
- data/lib/pubid/oiml/urn_generator.rb +22 -0
- data/lib/pubid/oiml.rb +5 -0
- data/lib/pubid/plateau.rb +5 -0
- data/lib/pubid/prefixes_support.rb +51 -0
- data/lib/pubid/sae.rb +6 -0
- data/lib/pubid/version.rb +1 -1
- data/lib/pubid.rb +68 -0
- metadata +7 -1
data/lib/pubid/ieee.rb
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Ieee
|
|
5
|
+
extend Pubid::PrefixesSupport
|
|
6
|
+
|
|
7
|
+
# Publisher prefix. The private PreParser::PUBLISHERS list also holds adopted
|
|
8
|
+
# co-org names (ANSI, AIEE, ASA, ...) that are not leading routing tokens for
|
|
9
|
+
# an IEEE reference, so they are deliberately excluded. The joint ISO/IEC/IEEE
|
|
10
|
+
# form comes from Pubid::JOINT_PREFIXES.
|
|
11
|
+
PREFIXES = ["IEEE"].freeze
|
|
12
|
+
|
|
5
13
|
autoload :Aiee, "#{__dir__}/ieee/aiee"
|
|
6
14
|
autoload :Builder, "#{__dir__}/ieee/builder"
|
|
7
15
|
autoload :Identifier, "#{__dir__}/ieee/identifier"
|
data/lib/pubid/iho.rb
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Iho
|
|
5
|
+
extend Pubid::PrefixesSupport
|
|
6
|
+
|
|
7
|
+
# Sole IHO publisher token (see the parser grammar).
|
|
8
|
+
PREFIXES = ["IHO"].freeze
|
|
9
|
+
|
|
5
10
|
autoload :Builder, "#{__dir__}/iho/builder"
|
|
6
11
|
autoload :Identifier, "#{__dir__}/iho/identifier"
|
|
7
12
|
autoload :Identifiers, "#{__dir__}/iho/identifiers"
|
data/lib/pubid/iso.rb
CHANGED
|
@@ -4,6 +4,12 @@ require "lutaml/model"
|
|
|
4
4
|
|
|
5
5
|
module Pubid
|
|
6
6
|
module Iso
|
|
7
|
+
extend Pubid::PrefixesSupport
|
|
8
|
+
|
|
9
|
+
# Sole ISO publisher token. Joint / co-publication forms (ISO/IEC, IEC/ISO,
|
|
10
|
+
# ISO/IEC/IEEE) are contributed symmetrically via Pubid::JOINT_PREFIXES.
|
|
11
|
+
PREFIXES = ["ISO"].freeze
|
|
12
|
+
|
|
7
13
|
autoload :Builder, "#{__dir__}/iso/builder"
|
|
8
14
|
autoload :BundledIdentifier, "#{__dir__}/iso/bundled_identifier"
|
|
9
15
|
autoload :CombinedIdentifier, "#{__dir__}/iso/combined_identifier"
|
data/lib/pubid/itu.rb
CHANGED
|
@@ -4,6 +4,11 @@ require "lutaml/model"
|
|
|
4
4
|
|
|
5
5
|
module Pubid
|
|
6
6
|
module Itu
|
|
7
|
+
extend Pubid::PrefixesSupport
|
|
8
|
+
|
|
9
|
+
# Sole ITU publisher token (see the parser's `itu_prefix` rule).
|
|
10
|
+
PREFIXES = ["ITU"].freeze
|
|
11
|
+
|
|
7
12
|
autoload :Builder, "#{__dir__}/itu/builder"
|
|
8
13
|
autoload :Components, "#{__dir__}/itu/components"
|
|
9
14
|
autoload :Identifier, "#{__dir__}/itu/identifier"
|
data/lib/pubid/jcgm.rb
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Jcgm
|
|
5
|
+
extend Pubid::PrefixesSupport
|
|
6
|
+
|
|
7
|
+
# Sole JCGM publisher token (see the parser's `publisher` rule).
|
|
8
|
+
PREFIXES = ["JCGM"].freeze
|
|
9
|
+
|
|
5
10
|
autoload :Builder, "#{__dir__}/jcgm/builder"
|
|
6
11
|
autoload :Components, "#{__dir__}/jcgm/components"
|
|
7
12
|
autoload :Identifier, "#{__dir__}/jcgm/identifier"
|
data/lib/pubid/jis.rb
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Jis
|
|
5
|
+
extend Pubid::PrefixesSupport
|
|
6
|
+
|
|
7
|
+
# Sole JIS publisher token (see the parser's `jis_prefix` rule). Document
|
|
8
|
+
# types (TR/TS) follow the JIS prefix and are excluded.
|
|
9
|
+
PREFIXES = ["JIS"].freeze
|
|
10
|
+
|
|
5
11
|
autoload :Builder, "#{__dir__}/jis/builder"
|
|
6
12
|
autoload :Identifier, "#{__dir__}/jis/identifier"
|
|
7
13
|
autoload :Identifiers, "#{__dir__}/jis/identifiers"
|
data/lib/pubid/nist.rb
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Nist
|
|
5
|
+
extend Pubid::PrefixesSupport
|
|
6
|
+
|
|
7
|
+
# Publisher prefixes (NBS/NIST) plus the "simple series" tokens that can
|
|
8
|
+
# *begin* a printed reference on their own — mirrors the parser's
|
|
9
|
+
# `publisher` and `simple_series` rules (lib/pubid/nist/parser.rb). Compound
|
|
10
|
+
# series that only appear glued to a publisher (e.g. "NBS CRPL-F-A") are
|
|
11
|
+
# excluded because they never lead a routable reference by themselves.
|
|
12
|
+
PREFIXES = %w[
|
|
13
|
+
NIST NBS
|
|
14
|
+
AMS VTS BSS BMS BH FIPS GCR HB MONO MP NCSTAR NSRDS IR SP TN CSWP
|
|
15
|
+
AI CIRC CS CSM CRPL LCIRC OWMWP PC RPT SIBS TIBM TTB EAB JPCRD JRES
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
5
18
|
autoload :Builder, "#{__dir__}/nist/builder"
|
|
6
19
|
autoload :Caster, "#{__dir__}/nist/caster"
|
|
7
20
|
autoload :CircularSupplementBuilder, "#{__dir__}/nist/circular_supplement_builder"
|
data/lib/pubid/oiml/builder.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Pubid
|
|
|
6
6
|
# Type to identifier class mapping (MECE)
|
|
7
7
|
TYPE_CLASS_MAP = {
|
|
8
8
|
"B" => "BasicPublication",
|
|
9
|
+
"Bulletin" => "Bulletin",
|
|
9
10
|
"D" => "Document",
|
|
10
11
|
"E" => "ExpertReport",
|
|
11
12
|
"G" => "Guide",
|
|
@@ -175,12 +176,26 @@ module Pubid
|
|
|
175
176
|
identifier.date = Pubid::Components::Date.new(year: year_value.to_s)
|
|
176
177
|
end
|
|
177
178
|
|
|
179
|
+
# Bulletin-specific locator fields. The structured form captures
|
|
180
|
+
# issue/sequence directly; the citation form captures volume/issue/
|
|
181
|
+
# article_id, which we decode to the same (year, issue, sequence)
|
|
182
|
+
# tuple. Year comes from either the date rule (structured) or the
|
|
183
|
+
# article_id (citation).
|
|
184
|
+
if identifier.is_a?(Identifiers::Bulletin)
|
|
185
|
+
apply_bulletin_locator(identifier, parsed_hash)
|
|
186
|
+
end
|
|
187
|
+
|
|
178
188
|
# Determine parsed format for round-trip fidelity
|
|
179
189
|
# If edition_format was captured, it means "Edition" text was present
|
|
180
190
|
identifier.parsed_format = if parsed_hash[:edition_format]
|
|
181
191
|
"long"
|
|
182
192
|
elsif parsed_hash[:space_before_lang]
|
|
183
193
|
"short_with_space"
|
|
194
|
+
elsif parsed_hash[:article_id]
|
|
195
|
+
# Bulletin citation form — preserve on
|
|
196
|
+
# the identifier so default to_s gives
|
|
197
|
+
# back the citation string verbatim.
|
|
198
|
+
"citation"
|
|
184
199
|
else
|
|
185
200
|
"short"
|
|
186
201
|
end
|
|
@@ -195,6 +210,24 @@ module Pubid
|
|
|
195
210
|
identifier
|
|
196
211
|
end
|
|
197
212
|
|
|
213
|
+
# Populate issue/sequence on a Bulletin from either parse shape.
|
|
214
|
+
# Structured: :issue and :sequence captured directly as zero-padded
|
|
215
|
+
# strings. Citation: :volume_roman, :issue_arabic, :article_id —
|
|
216
|
+
# decode the 8-digit article_id to recover year+issue+sequence.
|
|
217
|
+
def apply_bulletin_locator(identifier, parsed_hash)
|
|
218
|
+
if parsed_hash[:article_id]
|
|
219
|
+
article_id = parsed_hash[:article_id].to_s
|
|
220
|
+
identifier.date ||= Pubid::Components::Date.new
|
|
221
|
+
identifier.date.year = article_id[0, 4]
|
|
222
|
+
identifier.issue = article_id[4, 2]
|
|
223
|
+
identifier.sequence = article_id[6, 2]
|
|
224
|
+
return
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
identifier.issue = parsed_hash[:issue].to_s if parsed_hash[:issue]
|
|
228
|
+
identifier.sequence = parsed_hash[:sequence].to_s if parsed_hash[:sequence]
|
|
229
|
+
end
|
|
230
|
+
|
|
198
231
|
def extract_language(lang_data)
|
|
199
232
|
# Handle both direct string and nested hash from parser
|
|
200
233
|
case lang_data
|
|
@@ -10,6 +10,7 @@ module Pubid
|
|
|
10
10
|
OIML_TYPE_MAP = {
|
|
11
11
|
"pubid:oiml:recommendation" => "Pubid::Oiml::Identifiers::Recommendation",
|
|
12
12
|
"pubid:oiml:basic-publication" => "Pubid::Oiml::Identifiers::BasicPublication",
|
|
13
|
+
"pubid:oiml:bulletin" => "Pubid::Oiml::Identifiers::Bulletin",
|
|
13
14
|
"pubid:oiml:document" => "Pubid::Oiml::Identifiers::Document",
|
|
14
15
|
"pubid:oiml:guide" => "Pubid::Oiml::Identifiers::Guide",
|
|
15
16
|
"pubid:oiml:vocabulary" => "Pubid::Oiml::Identifiers::Vocabulary",
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pubid
|
|
4
|
+
module Oiml
|
|
5
|
+
module Identifiers
|
|
6
|
+
# OIML Bulletin issues and articles. Carries no code — the locator is
|
|
7
|
+
# the (year, issue, sequence) tuple drawn from the issue's place in the
|
|
8
|
+
# periodical hierarchy.
|
|
9
|
+
#
|
|
10
|
+
# Two input forms are accepted, both referring to the same article:
|
|
11
|
+
#
|
|
12
|
+
# structured: "OIML Bulletin 2026-02-11"
|
|
13
|
+
# citation: "OIML Bulletin LXVII(2) 20260211"
|
|
14
|
+
#
|
|
15
|
+
# The structured form is the dataset's primary docid (sortable,
|
|
16
|
+
# deterministic). The citation form is what OIML prints on the article
|
|
17
|
+
# page (`Citation: AUTHOR YEAR OIML Bulletin VOLUME(ISSUE) ARTID`).
|
|
18
|
+
#
|
|
19
|
+
# Volume tracks year deterministically: 1960 = I, year - 1959 = volume.
|
|
20
|
+
# The 8-digit article id is YYYYNNSS — the same three values concatenated,
|
|
21
|
+
# so the citation form is decoded entirely from the article id; the
|
|
22
|
+
# roman volume and parenthesised issue are redundant display.
|
|
23
|
+
#
|
|
24
|
+
# Shapes in relaton-data-oiml:
|
|
25
|
+
# "OIML Bulletin" (periodical)
|
|
26
|
+
# "OIML Bulletin 1960" (volume / year)
|
|
27
|
+
# "OIML Bulletin 1960-03" (issue)
|
|
28
|
+
# "OIML Bulletin 1960-03-01" (article, structured)
|
|
29
|
+
# "OIML Bulletin LXVII(2) 20260211" (article, citation)
|
|
30
|
+
class Bulletin < SingleIdentifier
|
|
31
|
+
# OIML Bulletin volume I was issued in 1960. Volume N corresponds to
|
|
32
|
+
# year (N + BASE_YEAR_OFFSET). Used to translate between the year
|
|
33
|
+
# carried in the structured form and the roman volume in citations.
|
|
34
|
+
BASE_YEAR_OFFSET = 1959
|
|
35
|
+
|
|
36
|
+
# Zero-padded issue number within the year ("01".."04", plus "07"/"10"
|
|
37
|
+
# for the online-bulletin series). Always 2 digits.
|
|
38
|
+
attribute :issue, :string
|
|
39
|
+
# Zero-padded sequence within the issue ("00" = editorial, "01"+ for
|
|
40
|
+
# articles). Always 2 digits.
|
|
41
|
+
attribute :sequence, :string
|
|
42
|
+
|
|
43
|
+
key_value do
|
|
44
|
+
map "issue", to: :issue
|
|
45
|
+
map "sequence", to: :sequence
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def type_string
|
|
49
|
+
"Bulletin"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Volume as an arabic string ("67"), derived from the year. nil when
|
|
53
|
+
# the year is absent (bare periodical reference).
|
|
54
|
+
def volume_arabic
|
|
55
|
+
return nil unless date&.year
|
|
56
|
+
|
|
57
|
+
(date.year.to_i - BASE_YEAR_OFFSET).to_s
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Volume as a roman-numeral string ("LXVII"), derived from the year.
|
|
61
|
+
# nil when the year is absent.
|
|
62
|
+
def volume_roman
|
|
63
|
+
number = volume_arabic&.to_i
|
|
64
|
+
return nil unless number&.positive?
|
|
65
|
+
|
|
66
|
+
self.class.to_roman(number)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# 8-digit oiml.org article id ("20260211"). Composed by concatenating
|
|
70
|
+
# year + issue + sequence. nil unless all three are present.
|
|
71
|
+
def article_id
|
|
72
|
+
return nil unless date&.year && issue && sequence
|
|
73
|
+
|
|
74
|
+
"#{date.year}#{issue}#{sequence}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class << self
|
|
78
|
+
# Convert a positive integer to its roman-numeral representation.
|
|
79
|
+
# Used to render the citation form. The dataset already stores the
|
|
80
|
+
# roman volume verbatim in `extent.locality`, so this conversion is
|
|
81
|
+
# only needed when rendering from the structured form.
|
|
82
|
+
def to_roman(number)
|
|
83
|
+
raise ArgumentError, "volume must be > 0" unless number.positive?
|
|
84
|
+
|
|
85
|
+
ROMAN_TABLE.each_with_object(+"") do |(value, sym), result|
|
|
86
|
+
quotient, number = number.divmod(value)
|
|
87
|
+
result << (sym * quotient)
|
|
88
|
+
end.freeze
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
ROMAN_TABLE = [
|
|
92
|
+
[1000, "M"], [900, "CM"], [500, "D"], [400, "CD"],
|
|
93
|
+
[100, "C"], [90, "XC"], [50, "L"], [40, "XL"],
|
|
94
|
+
[10, "X"], [9, "IX"], [5, "V"], [4, "IV"],
|
|
95
|
+
[1, "I"]
|
|
96
|
+
].freeze
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -6,6 +6,7 @@ module Pubid
|
|
|
6
6
|
autoload :Amendment, "#{__dir__}/identifiers/amendment"
|
|
7
7
|
autoload :Annex, "#{__dir__}/identifiers/annex"
|
|
8
8
|
autoload :BasicPublication, "#{__dir__}/identifiers/basic_publication"
|
|
9
|
+
autoload :Bulletin, "#{__dir__}/identifiers/bulletin"
|
|
9
10
|
autoload :Document, "#{__dir__}/identifiers/document"
|
|
10
11
|
autoload :Errata, "#{__dir__}/identifiers/errata"
|
|
11
12
|
autoload :ExpertReport, "#{__dir__}/identifiers/expert_report"
|
data/lib/pubid/oiml/parser.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Pubid
|
|
|
20
20
|
rule(:identifier) do
|
|
21
21
|
amendment_identifier | amendment_short | annex_letter_identifier |
|
|
22
22
|
annex_identifier | plus_supplement_identifier |
|
|
23
|
-
trailing_supplement_identifier | base_identifier
|
|
23
|
+
trailing_supplement_identifier | bulletin_identifier | base_identifier
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# Publisher - always "OIML"
|
|
@@ -29,6 +29,48 @@ module Pubid
|
|
|
29
29
|
# Document type - single letter
|
|
30
30
|
rule(:doc_type) { match("[BDEGRSVX]").as(:type) >> space }
|
|
31
31
|
|
|
32
|
+
# Bulletin locator — structured form. Year optionally followed by
|
|
33
|
+
# 2-digit issue and 2-digit sequence:
|
|
34
|
+
# "OIML Bulletin", "OIML Bulletin 1960",
|
|
35
|
+
# "OIML Bulletin 1960-03", "OIML Bulletin 1960-03-01"
|
|
36
|
+
# All four shapes appear as primary docids in relaton-data-oiml.
|
|
37
|
+
rule(:bulletin_date) do
|
|
38
|
+
space >> year_digits.as(:year) >>
|
|
39
|
+
(dash >> two_digits.as(:issue)).maybe >>
|
|
40
|
+
(dash >> two_digits.as(:sequence)).maybe
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Two-digit zero-padded number (used for issue / sequence).
|
|
44
|
+
rule(:two_digits) { match('\d').repeat(2, 2) }
|
|
45
|
+
|
|
46
|
+
# Roman numeral token composed of I,V,X,L,C,D,M (uppercase, matching
|
|
47
|
+
# OIML's print convention). Consumed but not captured — the 8-digit
|
|
48
|
+
# article id in the same citation carries year/issue/sequence
|
|
49
|
+
# deterministically, so the roman volume is redundant for parsing.
|
|
50
|
+
rule(:roman_numeral) { match("[IVXLCDM]").repeat(1) }
|
|
51
|
+
|
|
52
|
+
# Bulletin locator — citation form. The format OIML prints on the
|
|
53
|
+
# article page is: "LXVII(2) 20260211" where LXVII is the volume in
|
|
54
|
+
# roman numerals, (2) is the issue in arabic without zero padding,
|
|
55
|
+
# and 20260211 is the 8-digit oiml.org article id (YYYYNNSS).
|
|
56
|
+
rule(:bulletin_citation) do
|
|
57
|
+
space >> roman_numeral >>
|
|
58
|
+
lparen >> digits.as(:issue_arabic) >> rparen >>
|
|
59
|
+
space >> match('\d').repeat(8, 8).as(:article_id)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Bulletin identifier — no code; the locator (when present) is the
|
|
63
|
+
# (year, issue, sequence) tuple drawn from either the structured
|
|
64
|
+
# YYYY-II-SS form or the citation VOLUME(ISSUE) ARTID form. Both
|
|
65
|
+
# decode to the same record. Tried before base_identifier because
|
|
66
|
+
# "Bulletin" is a word that the single-letter doc_type rule cannot
|
|
67
|
+
# match.
|
|
68
|
+
rule(:bulletin_identifier) do
|
|
69
|
+
publisher >> str("Bulletin").as(:type) >>
|
|
70
|
+
(bulletin_citation | bulletin_date).maybe >>
|
|
71
|
+
language_portion.maybe.as(:language)
|
|
72
|
+
end
|
|
73
|
+
|
|
32
74
|
# Number with optional part and subpart
|
|
33
75
|
rule(:number_only) { digits.as(:number) }
|
|
34
76
|
|
data/lib/pubid/oiml/renderer.rb
CHANGED
|
@@ -20,6 +20,8 @@ module Pubid
|
|
|
20
20
|
case id
|
|
21
21
|
when Identifiers::Annex
|
|
22
22
|
render_annex(id)
|
|
23
|
+
when Identifiers::Bulletin
|
|
24
|
+
render_bulletin(id)
|
|
23
25
|
when SupplementIdentifier
|
|
24
26
|
render_supplement(id)
|
|
25
27
|
when SingleIdentifier
|
|
@@ -31,6 +33,55 @@ module Pubid
|
|
|
31
33
|
|
|
32
34
|
private
|
|
33
35
|
|
|
36
|
+
# Render the Bulletin in the requested or parsed form. Default is the
|
|
37
|
+
# structured "YYYY-II-SS" form (the dataset's primary docid). The
|
|
38
|
+
# citation form ("LXVII(2) 20260211") is emitted when the user asks
|
|
39
|
+
# for it via `to_s(format: :citation)` or when the identifier was
|
|
40
|
+
# itself parsed from a citation string (parsed_format == "citation").
|
|
41
|
+
def render_bulletin(id)
|
|
42
|
+
if citation_form?(id)
|
|
43
|
+
render_bulletin_citation(id)
|
|
44
|
+
else
|
|
45
|
+
render_bulletin_structured(id)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# User's explicit format request takes precedence over the parsed
|
|
50
|
+
# form, so `parse("LXVII(2) 20260211").to_s(format: :short)` returns
|
|
51
|
+
# the structured form. With no explicit request, the parsed form is
|
|
52
|
+
# preserved for round-trip fidelity.
|
|
53
|
+
def citation_form?(id)
|
|
54
|
+
case id.requested_format
|
|
55
|
+
when :citation then true
|
|
56
|
+
when :short, :structured then false
|
|
57
|
+
else
|
|
58
|
+
id.parsed_format == "citation"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# "OIML Bulletin" / "OIML Bulletin 1960" / "OIML Bulletin 1960-03" /
|
|
63
|
+
# "OIML Bulletin 1960-03-01". No code; the locator is space-separated.
|
|
64
|
+
def render_bulletin_structured(id)
|
|
65
|
+
result = "#{id.publisher} Bulletin"
|
|
66
|
+
if id.date&.year
|
|
67
|
+
result += " #{id.date.year}"
|
|
68
|
+
result += "-#{id.issue}" if id.issue
|
|
69
|
+
result += "-#{id.sequence}" if id.sequence
|
|
70
|
+
end
|
|
71
|
+
result += " (#{id.language})" if id.language
|
|
72
|
+
result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# "OIML Bulletin LXVII(2) 20260211". Only emitted for full articles
|
|
76
|
+
# (year + issue + sequence all present); lower tiers fall back to
|
|
77
|
+
# structured since OIML's citation format doesn't define volume-only
|
|
78
|
+
# or issue-only variants.
|
|
79
|
+
def render_bulletin_citation(id)
|
|
80
|
+
return render_bulletin_structured(id) unless id.date&.year && id.issue && id.sequence
|
|
81
|
+
|
|
82
|
+
"#{id.publisher} Bulletin #{id.volume_roman}(#{id.issue.to_i}) #{id.article_id}"
|
|
83
|
+
end
|
|
84
|
+
|
|
34
85
|
def effective_format(id)
|
|
35
86
|
id.requested_format ||
|
|
36
87
|
(id.parsed_format == "long" ? :long : :short)
|
|
@@ -28,6 +28,17 @@ module Pubid
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def generate
|
|
31
|
+
# Bulletin issues carry no code; the (year, issue, sequence) tuple
|
|
32
|
+
# is the locator. URNs are canonical regardless of how the input was
|
|
33
|
+
# formatted, so we always emit the structured YYYY-II-SS form even
|
|
34
|
+
# when the identifier was parsed from a citation string.
|
|
35
|
+
if identifier.is_a?(Identifiers::Bulletin)
|
|
36
|
+
parts = ["urn", "oiml", "bulletin"]
|
|
37
|
+
parts << bulletin_locator if identifier.date&.present?
|
|
38
|
+
parts << urn_language if urn_language
|
|
39
|
+
return parts.join(":")
|
|
40
|
+
end
|
|
41
|
+
|
|
31
42
|
parts = ["urn", "oiml"]
|
|
32
43
|
parts << urn_type
|
|
33
44
|
parts << urn_number if urn_number
|
|
@@ -51,6 +62,17 @@ module Pubid
|
|
|
51
62
|
|
|
52
63
|
parts.join(":")
|
|
53
64
|
end
|
|
65
|
+
|
|
66
|
+
# Structured locator "YYYY-II-SS" (or "YYYY-II" / "YYYY") for a Bulletin.
|
|
67
|
+
# Built directly from the date year and the issue/sequence attributes so
|
|
68
|
+
# the URN is identical regardless of whether the input was the structured
|
|
69
|
+
# or citation form.
|
|
70
|
+
def bulletin_locator
|
|
71
|
+
locator = identifier.date.year.to_s
|
|
72
|
+
locator += "-#{identifier.issue}" if identifier.issue
|
|
73
|
+
locator += "-#{identifier.sequence}" if identifier.sequence
|
|
74
|
+
locator
|
|
75
|
+
end
|
|
54
76
|
end
|
|
55
77
|
end
|
|
56
78
|
end
|
data/lib/pubid/oiml.rb
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Oiml
|
|
5
|
+
extend Pubid::PrefixesSupport
|
|
6
|
+
|
|
7
|
+
# Sole OIML publisher token (see the parser's `publisher` rule).
|
|
8
|
+
PREFIXES = ["OIML"].freeze
|
|
9
|
+
|
|
5
10
|
autoload :Builder, "#{__dir__}/oiml/builder"
|
|
6
11
|
autoload :Components, "#{__dir__}/oiml/components"
|
|
7
12
|
autoload :Identifier, "#{__dir__}/oiml/identifier"
|
data/lib/pubid/plateau.rb
CHANGED
|
@@ -4,6 +4,11 @@ require "parslet"
|
|
|
4
4
|
|
|
5
5
|
module Pubid
|
|
6
6
|
module Plateau
|
|
7
|
+
extend Pubid::PrefixesSupport
|
|
8
|
+
|
|
9
|
+
# Sole PLATEAU publisher token (see the parser's `publisher` rule).
|
|
10
|
+
PREFIXES = ["PLATEAU"].freeze
|
|
11
|
+
|
|
7
12
|
autoload :Builder, "#{__dir__}/plateau/builder"
|
|
8
13
|
autoload :Identifier, "#{__dir__}/plateau/identifier"
|
|
9
14
|
autoload :Identifiers, "#{__dir__}/plateau/identifiers"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pubid
|
|
4
|
+
# Mixin providing the uniform, static +prefixes+ class method that every
|
|
5
|
+
# registered flavor exposes. relaton uses this to build a global prefix
|
|
6
|
+
# register that routes a printed reference string (e.g. +"DD 1234"+,
|
|
7
|
+
# +"ISO/IEC 8802"+) to the flavor(s) that own it.
|
|
8
|
+
#
|
|
9
|
+
# A flavor +extend+s this module and defines a frozen +PREFIXES+ array holding
|
|
10
|
+
# only *its own* leading tokens. Joint / co-publication tokens are injected
|
|
11
|
+
# centrally from {Pubid::JOINT_PREFIXES} so that a co-published prefix
|
|
12
|
+
# (e.g. +"ISO/IEC"+) is listed symmetrically by every co-publisher without
|
|
13
|
+
# being duplicated by hand in two files.
|
|
14
|
+
#
|
|
15
|
+
# == Inclusion policy
|
|
16
|
+
# +prefixes+ returns the set of *leading identifier prefix tokens* — the
|
|
17
|
+
# tokens a printed identifier can start with such that the string belongs to
|
|
18
|
+
# this SDO. Concretely it INCLUDES:
|
|
19
|
+
# * publisher prefixes (e.g. +ISO+, +IEC+, +NIST+, +NBS+, +BS+, +BSI+);
|
|
20
|
+
# * leading series/type tokens that can *begin* a printed reference on their
|
|
21
|
+
# own (e.g. BSI +DD+/+PD+, NIST +FIPS+/+SP+);
|
|
22
|
+
# * joint / co-publication forms (e.g. +ISO/IEC+, +ISO/IEC/IEEE+).
|
|
23
|
+
# It EXCLUDES:
|
|
24
|
+
# * pure sub-series that never appear without a publisher prefix;
|
|
25
|
+
# * dangerously-ambiguous single letters (e.g. BSI aerospace +A+/+B+/+C+),
|
|
26
|
+
# which would cause false routing.
|
|
27
|
+
module PrefixesSupport
|
|
28
|
+
# The static set of leading prefix tokens this flavor recognizes.
|
|
29
|
+
#
|
|
30
|
+
# Static: callable without parsing any identifier string. Strings are
|
|
31
|
+
# returned in canonical case exactly as printed (e.g. +"BS EN ISO"+,
|
|
32
|
+
# +"ISO/IEC"+); consumers fold case themselves.
|
|
33
|
+
#
|
|
34
|
+
# @return [Array<String>] frozen, de-duplicated list of prefix tokens
|
|
35
|
+
def prefixes
|
|
36
|
+
own = const_defined?(:PREFIXES) ? const_get(:PREFIXES) : []
|
|
37
|
+
joint = Pubid::JOINT_PREFIXES.fetch(prefix_flavor_key, [])
|
|
38
|
+
(own + joint).uniq.freeze
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The registry key for this flavor, derived from the module name
|
|
42
|
+
# (e.g. +Pubid::CenCenelec+ => +:cen_cenelec+). Used to look this flavor up
|
|
43
|
+
# in {Pubid::JOINT_PREFIXES} and to label it in {Pubid.prefix_flavors}.
|
|
44
|
+
#
|
|
45
|
+
# @return [Symbol]
|
|
46
|
+
def prefix_flavor_key
|
|
47
|
+
name.split("::").last
|
|
48
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.to_sym
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/pubid/sae.rb
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module Pubid
|
|
4
4
|
module Sae
|
|
5
|
+
extend Pubid::PrefixesSupport
|
|
6
|
+
|
|
7
|
+
# Sole SAE publisher token (see the parser's `publisher` rule). Document
|
|
8
|
+
# types (AMS/AIR/ARP/AS/J/MA) follow the SAE prefix and are excluded.
|
|
9
|
+
PREFIXES = ["SAE"].freeze
|
|
10
|
+
|
|
5
11
|
autoload :Builder, "#{__dir__}/sae/builder"
|
|
6
12
|
autoload :Components, "#{__dir__}/sae/components"
|
|
7
13
|
autoload :Identifier, "#{__dir__}/sae/identifier"
|
data/lib/pubid/version.rb
CHANGED
data/lib/pubid.rb
CHANGED
|
@@ -9,6 +9,11 @@ require "parslet"
|
|
|
9
9
|
# code runs.
|
|
10
10
|
require "pubid/lutaml/no_store_registration"
|
|
11
11
|
|
|
12
|
+
# Uniform, static per-flavor prefix API (Pubid::<Flavor>.prefixes). Required
|
|
13
|
+
# before the flavor autoloads below so each flavor file can `extend` it and the
|
|
14
|
+
# central JOINT_PREFIXES constant is visible when its PREFIXES is defined.
|
|
15
|
+
require "pubid/prefixes_support"
|
|
16
|
+
|
|
12
17
|
module Pubid
|
|
13
18
|
# Upper bound on the length of an identifier string accepted by any +parse+
|
|
14
19
|
# entry point. Real-world standards identifiers are well under 200 characters;
|
|
@@ -61,6 +66,21 @@ module Pubid
|
|
|
61
66
|
end
|
|
62
67
|
end
|
|
63
68
|
|
|
69
|
+
# Canonical joint / co-publication leading tokens, injected symmetrically into
|
|
70
|
+
# every participating flavor's +prefixes+ (see {PrefixesSupport}). Single
|
|
71
|
+
# source of truth: editing an entry here updates both sides at once
|
|
72
|
+
# (e.g. +"ISO/IEC"+ appears in +Pubid::Iso.prefixes+ and +Pubid::Iec.prefixes+),
|
|
73
|
+
# so co-publication symmetry can never drift. Keyed by
|
|
74
|
+
# {PrefixesSupport#prefix_flavor_key}.
|
|
75
|
+
JOINT_PREFIXES = {
|
|
76
|
+
iso: ["ISO/IEC", "IEC/ISO", "ISO/IEC/IEEE"],
|
|
77
|
+
iec: ["ISO/IEC", "IEC/ISO", "ISO/IEC/IEEE"],
|
|
78
|
+
ieee: ["ISO/IEC/IEEE"],
|
|
79
|
+
ansi: ["ANSI/ASHRAE", "ANSI/AMCA"],
|
|
80
|
+
ashrae: ["ANSI/ASHRAE"],
|
|
81
|
+
amca: ["ANSI/AMCA"],
|
|
82
|
+
}.freeze
|
|
83
|
+
|
|
64
84
|
autoload :Parser, "pubid/parser"
|
|
65
85
|
autoload :Components, "pubid/components"
|
|
66
86
|
autoload :BundledIdentifier, "pubid/bundled_identifier"
|
|
@@ -175,4 +195,52 @@ module Pubid
|
|
|
175
195
|
end
|
|
176
196
|
@flavors_loaded = true
|
|
177
197
|
end
|
|
198
|
+
|
|
199
|
+
# Static leading prefix tokens for a single flavor.
|
|
200
|
+
#
|
|
201
|
+
# @param flavor [Symbol, String] a registered flavor name (e.g. +:iso+)
|
|
202
|
+
# @return [Array<String>] the flavor's prefixes (see {PrefixesSupport})
|
|
203
|
+
# @raise [ArgumentError] if the flavor is not registered
|
|
204
|
+
def self.prefixes(flavor)
|
|
205
|
+
mod = Registry.get(flavor)
|
|
206
|
+
raise ArgumentError, "unknown flavor: #{flavor.inspect}" unless mod
|
|
207
|
+
|
|
208
|
+
mod.prefixes
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Reverse index mapping every canonical prefix token to the flavor(s) that
|
|
212
|
+
# own it — the exact routing table relaton needs. Co-published prefixes list
|
|
213
|
+
# every co-publisher (e.g. +"ISO/IEC" => [:iec, :iso]+); see the inclusion /
|
|
214
|
+
# exclusion policy documented on {PrefixesSupport}.
|
|
215
|
+
#
|
|
216
|
+
# The +:cen+ alias of +:cen_cenelec+ is de-duplicated by module identity, so a
|
|
217
|
+
# prefix is attributed to a flavor's canonical
|
|
218
|
+
# {PrefixesSupport#prefix_flavor_key} exactly once.
|
|
219
|
+
#
|
|
220
|
+
# @return [Hash{String => Array<Symbol>}] prefix token => sorted flavor keys,
|
|
221
|
+
# e.g. +{ "ISO" => [:iso], "ISO/IEC" => [:iec, :iso], ... }+
|
|
222
|
+
def self.prefix_flavors
|
|
223
|
+
index = Hash.new { |h, k| h[k] = [] }
|
|
224
|
+
each_prefix_flavor_module do |mod|
|
|
225
|
+
key = mod.prefix_flavor_key
|
|
226
|
+
mod.prefixes.each { |prefix| index[prefix] |= [key] }
|
|
227
|
+
end
|
|
228
|
+
index.transform_values(&:sort).sort.to_h
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Yields each unique flavor module that exposes +prefixes+ exactly once,
|
|
232
|
+
# collapsing alias registrations (e.g. +:cen+ and +:cen_cenelec+ both point to
|
|
233
|
+
# +Pubid::CenCenelec+) by module identity.
|
|
234
|
+
# @yieldparam mod [Module] a flavor module
|
|
235
|
+
def self.each_prefix_flavor_module
|
|
236
|
+
eager_load_flavors!
|
|
237
|
+
seen = {}
|
|
238
|
+
Registry.flavor_names.each do |flavor_name|
|
|
239
|
+
mod = Registry.get(flavor_name)
|
|
240
|
+
next if seen.key?(mod) || !mod.respond_to?(:prefixes)
|
|
241
|
+
|
|
242
|
+
seen[mod] = true
|
|
243
|
+
yield mod
|
|
244
|
+
end
|
|
245
|
+
end
|
|
178
246
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pubid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.0.pre.alpha.
|
|
4
|
+
version: 2.0.0.pre.alpha.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -339,8 +339,12 @@ files:
|
|
|
339
339
|
- lib/pubid/iala/builder.rb
|
|
340
340
|
- lib/pubid/iala/identifier.rb
|
|
341
341
|
- lib/pubid/iala/identifiers.rb
|
|
342
|
+
- lib/pubid/iala/identifiers/advice.rb
|
|
343
|
+
- lib/pubid/iala/identifiers/annex.rb
|
|
342
344
|
- lib/pubid/iala/identifiers/base.rb
|
|
345
|
+
- lib/pubid/iala/identifiers/general_assembly.rb
|
|
343
346
|
- lib/pubid/iala/identifiers/guideline.rb
|
|
347
|
+
- lib/pubid/iala/identifiers/letter.rb
|
|
344
348
|
- lib/pubid/iala/identifiers/manual.rb
|
|
345
349
|
- lib/pubid/iala/identifiers/model_course.rb
|
|
346
350
|
- lib/pubid/iala/identifiers/recommendation.rb
|
|
@@ -625,6 +629,7 @@ files:
|
|
|
625
629
|
- lib/pubid/oiml/identifiers/amendment.rb
|
|
626
630
|
- lib/pubid/oiml/identifiers/annex.rb
|
|
627
631
|
- lib/pubid/oiml/identifiers/basic_publication.rb
|
|
632
|
+
- lib/pubid/oiml/identifiers/bulletin.rb
|
|
628
633
|
- lib/pubid/oiml/identifiers/document.rb
|
|
629
634
|
- lib/pubid/oiml/identifiers/errata.rb
|
|
630
635
|
- lib/pubid/oiml/identifiers/expert_report.rb
|
|
@@ -656,6 +661,7 @@ files:
|
|
|
656
661
|
- lib/pubid/plateau/supplement_identifier.rb
|
|
657
662
|
- lib/pubid/plateau/urn_generator.rb
|
|
658
663
|
- lib/pubid/plateau/urn_parser.rb
|
|
664
|
+
- lib/pubid/prefixes_support.rb
|
|
659
665
|
- lib/pubid/renderers.rb
|
|
660
666
|
- lib/pubid/renderers/base.rb
|
|
661
667
|
- lib/pubid/renderers/directives_renderer.rb
|