pubid 2.0.1 → 2.0.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/parg/adobe.json +1 -1
- data/data/parg/amca.json +1 -1
- data/data/parg/ansi.json +1 -1
- data/data/parg/api.json +1 -1
- data/data/parg/ashrae.json +1 -1
- data/data/parg/asme.json +1 -1
- data/data/parg/astm.json +1 -1
- data/data/parg/bipm.json +1 -1
- data/data/parg/bsi.json +1 -1
- data/data/parg/ccsds.json +1 -1
- data/data/parg/cen_cenelec.json +1 -1
- data/data/parg/cie.json +1 -1
- data/data/parg/csa.json +1 -1
- data/data/parg/ecma.json +1 -1
- data/data/parg/etsi.json +1 -1
- data/data/parg/evs.json +1 -1
- data/data/parg/gost.json +1 -1
- data/data/parg/iana.json +1 -1
- data/data/parg/idf.json +1 -1
- data/data/parg/iec.json +1 -1
- data/data/parg/iho.json +1 -0
- data/data/parg/isbn.json +1 -1
- data/data/parg/itu.json +1 -1
- data/data/parg/jcgm.json +1 -1
- data/data/parg/jis.json +1 -1
- data/data/parg/nist.json +1 -1
- data/data/parg/oiml.json +1 -1
- data/data/parg/omg.json +1 -1
- data/data/parg/plateau.json +1 -1
- data/data/parg/tgpp.json +1 -1
- data/data/parg/un.json +1 -1
- data/lib/pubid/adobe/identifier.rb +1 -1
- data/lib/pubid/adobe/parser.rb +2 -113
- data/lib/pubid/amca/identifiers/base.rb +1 -1
- data/lib/pubid/amca/parser.rb +1 -145
- data/lib/pubid/ansi/identifier.rb +1 -1
- data/lib/pubid/ansi/parser.rb +1 -82
- data/lib/pubid/api/builder.rb +32 -1
- data/lib/pubid/api/identifier.rb +1 -1
- data/lib/pubid/api/parser.rb +1 -131
- data/lib/pubid/ashrae/identifiers/base.rb +1 -1
- data/lib/pubid/ashrae/parser.rb +3 -710
- data/lib/pubid/asme/builder.rb +6 -4
- data/lib/pubid/asme/identifier.rb +1 -2
- data/lib/pubid/asme/parser.rb +1 -328
- data/lib/pubid/astm/builder.rb +12 -3
- data/lib/pubid/astm/identifier.rb +1 -2
- data/lib/pubid/astm/parser.rb +1 -227
- data/lib/pubid/bipm/identifier.rb +8 -1
- data/lib/pubid/bipm/parser.rb +2 -224
- data/lib/pubid/bsi/builder.rb +2 -4
- data/lib/pubid/bsi/parser.rb +1 -666
- data/lib/pubid/bsi/single_identifier.rb +38 -1
- data/lib/pubid/calconnect/parser.rb +2 -40
- data/lib/pubid/ccsds/identifier.rb +1 -1
- data/lib/pubid/ccsds/parser.rb +1 -66
- data/lib/pubid/cen_cenelec/identifier.rb +16 -1
- data/lib/pubid/cen_cenelec/parser.rb +3 -136
- data/lib/pubid/cie/identifier.rb +1 -1
- data/lib/pubid/cie/parser.rb +3 -378
- data/lib/pubid/conformance.rb +1 -1
- data/lib/pubid/csa/builder.rb +41 -18
- data/lib/pubid/csa/identifier.rb +12 -4
- data/lib/pubid/csa/identifiers/amendment.rb +28 -0
- data/lib/pubid/csa/identifiers.rb +1 -0
- data/lib/pubid/csa/parser.rb +16 -397
- data/lib/pubid/doi/parser.rb +2 -37
- data/lib/pubid/easc/builder.rb +1 -1
- data/lib/pubid/easc/parser.rb +2 -70
- data/lib/pubid/ecma/CLAUDE.md +8 -1
- data/lib/pubid/ecma/identifier.rb +13 -8
- data/lib/pubid/ecma/parser.rb +2 -54
- data/lib/pubid/ecma/renderer.rb +3 -7
- data/lib/pubid/errors.rb +18 -13
- data/lib/pubid/etsi/builder.rb +8 -5
- data/lib/pubid/etsi/identifiers/base.rb +1 -1
- data/lib/pubid/etsi/parser.rb +1 -132
- data/lib/pubid/evs/identifier.rb +7 -0
- data/lib/pubid/evs/parser.rb +1 -8
- data/lib/pubid/evs.rb +1 -1
- data/lib/pubid/gb/identifier.rb +6 -0
- data/lib/pubid/gb/parser.rb +2 -51
- data/lib/pubid/gost/identifier.rb +9 -1
- data/lib/pubid/gost/parser.rb +4 -81
- data/lib/pubid/iala/parser.rb +2 -121
- data/lib/pubid/iana/identifier.rb +1 -1
- data/lib/pubid/iana/parser.rb +2 -24
- data/lib/pubid/identifier.rb +78 -1
- data/lib/pubid/idf/builder.rb +42 -0
- data/lib/pubid/idf/identifier.rb +1 -1
- data/lib/pubid/idf/parser.rb +1 -137
- data/lib/pubid/idf.rb +1 -2
- data/lib/pubid/iec/builder.rb +158 -50
- data/lib/pubid/iec/identifier.rb +1 -1
- data/lib/pubid/iec/parser.rb +3 -430
- data/lib/pubid/ieee/aiee/identifier.rb +2 -1
- data/lib/pubid/ieee/aiee.rb +0 -1
- data/lib/pubid/ieee/builder.rb +2 -2
- data/lib/pubid/ieee/identifiers/base.rb +1 -1
- data/lib/pubid/ieee/ire/identifier.rb +2 -1
- data/lib/pubid/ieee/ire.rb +0 -1
- data/lib/pubid/ieee/nesc.rb +0 -1
- data/lib/pubid/ieee/parser.rb +1 -1257
- data/lib/pubid/ietf/identifiers/base.rb +17 -1
- data/lib/pubid/ietf/parser.rb +2 -47
- data/lib/pubid/ietf/renderer.rb +17 -2
- data/lib/pubid/iho/CLAUDE.md +89 -0
- data/lib/pubid/iho/builder.rb +4 -1
- data/lib/pubid/iho/identifiers/base.rb +28 -2
- data/lib/pubid/iho/renderer.rb +8 -0
- data/lib/pubid/iho/urn_generator.rb +11 -1
- data/lib/pubid/iho/urn_parser.rb +21 -11
- data/lib/pubid/iho.rb +0 -1
- data/lib/pubid/isbn/identifier.rb +1 -1
- data/lib/pubid/isbn/parser.rb +2 -31
- data/lib/pubid/iso/builder.rb +1 -1
- data/lib/pubid/iso/identifiers/directives.rb +6 -3
- data/lib/pubid/iso/identifiers/guide.rb +6 -3
- data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +6 -3
- data/lib/pubid/iso/normalizer.rb +1 -1
- data/lib/pubid/iso/parser.rb +1 -539
- data/lib/pubid/iso/supplement_identifier.rb +6 -3
- data/lib/pubid/iso/utilities.rb +4 -5
- data/lib/pubid/iso.rb +1 -19
- data/lib/pubid/itu/builder.rb +36 -20
- data/lib/pubid/itu/identifiers/base.rb +15 -2
- data/lib/pubid/itu/parser.rb +1 -775
- data/lib/pubid/jcgm/builder.rb +35 -0
- data/lib/pubid/jcgm/parser.rb +1 -151
- data/lib/pubid/jcgm.rb +1 -2
- data/lib/pubid/jis/builder.rb +18 -29
- data/lib/pubid/jis/identifier.rb +1 -1
- data/lib/pubid/jis/parser.rb +1 -128
- data/lib/pubid/nist/builder.rb +5 -1
- data/lib/pubid/nist/caster.rb +13 -3
- data/lib/pubid/nist/identifiers/base.rb +37 -2
- data/lib/pubid/nist/identifiers/circular.rb +2 -2
- data/lib/pubid/nist/identifiers/circular_supplement.rb +2 -2
- data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +2 -2
- data/lib/pubid/nist/identifiers/crpl_report.rb +2 -2
- data/lib/pubid/nist/identifiers/handbook.rb +2 -2
- data/lib/pubid/nist/identifiers/internal_report.rb +2 -2
- data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +2 -2
- data/lib/pubid/nist/identifiers/monograph.rb +2 -2
- data/lib/pubid/nist/identifiers/report.rb +2 -2
- data/lib/pubid/nist/parser.rb +19 -775
- data/lib/pubid/nist/router.rb +4 -1
- data/lib/pubid/nist/supplement_identifier.rb +2 -2
- data/lib/pubid/oasis/parser.rb +2 -16
- data/lib/pubid/ogc/parser.rb +2 -26
- data/lib/pubid/oiml/identifiers/certification_system.rb +7 -0
- data/lib/pubid/oiml/identifiers/dual_published.rb +2 -2
- data/lib/pubid/oiml/parser.rb +1 -303
- data/lib/pubid/oiml.rb +1 -2
- data/lib/pubid/omg/identifier.rb +6 -1
- data/lib/pubid/omg/parser.rb +2 -76
- data/lib/pubid/parser.rb +5 -4
- data/lib/pubid/parsers/mr_string.rb +1 -1
- data/lib/pubid/plateau/parser.rb +1 -66
- data/lib/pubid/plateau.rb +1 -2
- data/lib/pubid/renderers/directives_renderer.rb +1 -1
- data/lib/pubid/renderers/iwa_renderer.rb +1 -1
- data/lib/pubid/renderers/publisher_stripper.rb +82 -0
- data/lib/pubid/renderers/supplement_renderer.rb +1 -1
- data/lib/pubid/renderers.rb +1 -0
- data/lib/pubid/sae/parser.rb +1 -48
- data/lib/pubid/tgpp/identifier.rb +1 -1
- data/lib/pubid/tgpp/parser.rb +2 -59
- data/lib/pubid/un/identifier.rb +1 -1
- data/lib/pubid/un/parser.rb +1 -18
- data/lib/pubid/version.rb +1 -1
- data/lib/pubid/w3c/parser.rb +2 -27
- data/lib/pubid/xsf/identifier.rb +10 -6
- data/lib/pubid/xsf/parser.rb +3 -31
- data/lib/pubid/xsf/renderer.rb +2 -4
- data/lib/pubid.rb +8 -9
- metadata +6 -23
- data/lib/pubid/ieee/aiee/parser.rb +0 -130
- data/lib/pubid/ieee/ire/parser.rb +0 -109
- data/lib/pubid/ieee/nesc/parser.rb +0 -155
- data/lib/pubid/iho/parser.rb +0 -68
- data/lib/pubid/parser/common_parse_methods.rb +0 -13
- data/lib/pubid/parser/common_parse_rules.rb +0 -56
- data/lib/pubid/parser/grammar.rb +0 -96
data/lib/pubid/csa/parser.rb
CHANGED
|
@@ -1,382 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "parslet"
|
|
4
|
-
|
|
5
3
|
module Pubid
|
|
6
4
|
module Csa
|
|
7
|
-
class Parser
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
rule(:dash) { str("-") }
|
|
11
|
-
rule(:slash) { str("/") }
|
|
12
|
-
rule(:colon) { str(":") }
|
|
13
|
-
rule(:comma) { str(",") }
|
|
14
|
-
rule(:dot) { str(".") }
|
|
15
|
-
rule(:ampersand) { str("&") }
|
|
16
|
-
rule(:plus) { str(" + ") }
|
|
17
|
-
rule(:digit) { match("[0-9]") }
|
|
18
|
-
rule(:digits) { digit.repeat(1) }
|
|
19
|
-
rule(:letter) { match("[A-Z]") }
|
|
20
|
-
rule(:letters) { letter.repeat(1) }
|
|
21
|
-
|
|
22
|
-
# Publisher
|
|
23
|
-
rule(:publisher) do
|
|
24
|
-
str("CSA") >> space >> (str("").as(:has_publisher) | str("0").as(:has_publisher))
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Code pattern: letter + dotted numbers (e.g., B149.1, C22.2, A123.17)
|
|
28
|
-
# Can also have dash-number-letter suffix (e.g., for NO. numbers like 60950-1A)
|
|
29
|
-
# Can have letter suffixes like HB, CIICHB, SP at the end
|
|
30
|
-
# Can have multiple dash-number sequences (e.g., C13256-1-01)
|
|
31
|
-
# NEW: Also support pure numbers with HB suffix (e.g., 15189HB)
|
|
32
|
-
# NEW: Simple letter+number codes (e.g., Z240, A220) for Series identifiers
|
|
33
|
-
rule(:code_pattern) do
|
|
34
|
-
# Pattern 1: Pure dotted numbers - e.g., 12.4, 2.15, 3.11
|
|
35
|
-
(match("[0-9]").repeat(1) >> dot >> match("[0-9]").repeat(1) >>
|
|
36
|
-
# Allow single-dash suffix for part numbers (e.g., "12.4-1")
|
|
37
|
-
# But NOT 2-digit years which are handled separately
|
|
38
|
-
(dash >> match("[0-9]").repeat(1)).repeat(0, 1) >>
|
|
39
|
-
letter.repeat(2, 6).maybe).as(:code) |
|
|
40
|
-
# Pattern 2: Pure numbers with HB suffix - e.g., 15189HB (NEW)
|
|
41
|
-
(match("[0-9]").repeat(1) >> letter.repeat(2, 6)).as(:code) |
|
|
42
|
-
# Pattern 3: Letter + numbers with dots then dashes - e.g., C22.2-144, B149.1
|
|
43
|
-
(letter >> match("[0-9]").repeat(1) >>
|
|
44
|
-
(dot >> match("[0-9]").repeat(1)).repeat >> # Dot sections first
|
|
45
|
-
# Dash sections after - allow part numbers but not 2-digit years
|
|
46
|
-
# Single digit dash = part number (e.g., "-1", "-2")
|
|
47
|
-
# 3+ digits or letter suffix = part number (e.g., "-144", "-1A")
|
|
48
|
-
# But DON'T consume 2-digit years which are matched separately
|
|
49
|
-
(dash >> (
|
|
50
|
-
(match("[0-9]") >> match("[0-9]").absent?) | # exactly 1 digit (no digit follows)
|
|
51
|
-
match("[0-9]").repeat(3) | # 3+ digits = part number (e.g., "-144")
|
|
52
|
-
(match("[0-9]").repeat(1) >> letter.repeat(1)) # digit + letter = part number (e.g., "-1A")
|
|
53
|
-
)).repeat >>
|
|
54
|
-
letter.repeat(2, 6).maybe).as(:code) | # Allow HB, CIICHB, SP, etc.
|
|
55
|
-
# Pattern 4: Simple letter + number codes - e.g., Z240, A220, B140 (for Series)
|
|
56
|
-
(letter >> match("[0-9]").repeat(1) >> dot >> match("[0-9]").repeat(1).maybe).as(:code) |
|
|
57
|
-
# Pattern 5: Letter + multi-digit number without dots/dashes - e.g., Z240, A220
|
|
58
|
-
(letter >> match("[0-9]").repeat(2)).as(:code)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# NO. notation - "Number" designation within a series (e.g., C22.2 NO. 1)
|
|
62
|
-
rule(:no_notation) do
|
|
63
|
-
space >> (str("NO.") | str("No.")).as(:no_notation) >> space
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Number after NO. - can be complex (e.g., 60601-1-9, 144.1, 1010.2.031, 1, 60601-1)
|
|
67
|
-
# Note: Single-dash patterns allowed only when first part has 3+ digits
|
|
68
|
-
# to avoid consuming year patterns like "1-04" where "-04" is the year
|
|
69
|
-
rule(:no_number) do
|
|
70
|
-
(
|
|
71
|
-
# Multi-part with dashes (e.g., 60601-1-9, 1-9-2)
|
|
72
|
-
# Requires at least 2 dashes (3+ numeric parts) to distinguish from year patterns
|
|
73
|
-
(match("[0-9]").repeat(1) >>
|
|
74
|
-
(dash >> match("[0-9]").repeat(1)).repeat(2, 10)) |
|
|
75
|
-
# Single-dash pattern (e.g., 60601-1, 60079-11) - allowed if first part has 3+ digits
|
|
76
|
-
# This prevents consuming year patterns like "1-04"
|
|
77
|
-
# CRITICAL: 2-digit suffix allowed only when first part has 5+ digits
|
|
78
|
-
# (IEC-style numbers like 60601, 60079). 3-4 digit first parts with 2-digit
|
|
79
|
-
# suffixes are year patterns (e.g., "109-17" = number 109, year 2017).
|
|
80
|
-
(match("[0-9]").repeat(5) >> dash >> match("[0-9]").repeat(1)) | # 5+ digits: any suffix
|
|
81
|
-
(match("[0-9]").repeat(3, 4) >> dash >> (
|
|
82
|
-
(match("[0-9]") >> match("[0-9]").absent?) | # exactly 1 digit (e.g., -1)
|
|
83
|
-
match("[0-9]").repeat(3) # 3+ digits (e.g., -144)
|
|
84
|
-
)) |
|
|
85
|
-
# Multi-part dotted number (e.g., 144.1, 1010.2.031, 1010.2.031.5)
|
|
86
|
-
# Can have 1-3 dots (2-4 numeric parts)
|
|
87
|
-
(match("[0-9]").repeat(1) >>
|
|
88
|
-
(dot >> match("[0-9]").repeat(1)).repeat(1, 3)) |
|
|
89
|
-
# Simple number (e.g., 1, 286)
|
|
90
|
-
match("[0-9]").repeat(1)
|
|
91
|
-
).as(:no_number)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Year format with optional F or M prefix
|
|
95
|
-
rule(:year_prefix) { (str("F") | str("M")).as(:year_prefix).maybe }
|
|
96
|
-
rule(:year_2digit) { digit.repeat(2, 2) }
|
|
97
|
-
rule(:year_4digit) { digit.repeat(4, 4) }
|
|
98
|
-
|
|
99
|
-
# Year with colon (modern format) - mark as colon_year
|
|
100
|
-
# Support both with prefix (:M04) and without prefix (:04)
|
|
101
|
-
rule(:colon_year) do
|
|
102
|
-
str("").as(:colon_format) >> # Mark that colon format was used
|
|
103
|
-
colon >> (
|
|
104
|
-
# With M/F prefix (e.g., :M04, :F04)
|
|
105
|
-
(year_prefix >> (year_4digit | year_2digit).as(:year)) |
|
|
106
|
-
# Without prefix (e.g., :04)
|
|
107
|
-
(year_4digit | year_2digit).as(:year)
|
|
108
|
-
)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# Year with dash (older format) - mark as dash_year
|
|
112
|
-
# Support both with prefix (-M04) and without prefix (-04)
|
|
113
|
-
rule(:dash_year) do
|
|
114
|
-
str("").as(:dash_format) >> # Mark that dash format was used
|
|
115
|
-
dash >> (
|
|
116
|
-
# With M/F prefix (e.g., -M04, -F04)
|
|
117
|
-
(year_prefix >> (year_4digit | year_2digit).as(:year)) |
|
|
118
|
-
# Without prefix (e.g., -04)
|
|
119
|
-
(year_4digit | year_2digit).as(:year)
|
|
120
|
-
)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Reaffirmation notation - supports both 2-digit and 4-digit years
|
|
124
|
-
# Tracks original format via :reaffirmation_4digit flag
|
|
125
|
-
rule(:reaffirmation) do
|
|
126
|
-
space >> str("(R") >>
|
|
127
|
-
(year_4digit.as(:reaffirmation_4digit) | year_2digit.as(:reaffirmation_2digit)) >>
|
|
128
|
-
str(")")
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Amendment notation (e.g., /A1:15, /A2:22, /Amd 1)
|
|
132
|
-
rule(:amendment_slash) do
|
|
133
|
-
slash >>
|
|
134
|
-
(str("Amd") >> space).maybe >>
|
|
135
|
-
str("A").maybe >>
|
|
136
|
-
digits.as(:amendment_number) >>
|
|
137
|
-
(colon >> year_2digit.as(:amendment_year)).maybe
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Package keywords
|
|
141
|
-
rule(:package_keyword) do
|
|
142
|
-
str("Code") | str("Handbook") | str("Training Package") | str("Package")
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Package portion: "Code, Handbook & Training Package"
|
|
146
|
-
rule(:package_portion) do
|
|
147
|
-
space >>
|
|
148
|
-
package_keyword >>
|
|
149
|
-
(
|
|
150
|
-
(comma >> space >> package_keyword) |
|
|
151
|
-
(space >> ampersand >> space >> package_keyword)
|
|
152
|
-
).repeat
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Series prefix: 2-3 letters before SERIES keyword (e.g., MH, RV)
|
|
156
|
-
rule(:series_prefix) do
|
|
157
|
-
letter.repeat(2, 3).as(:series_prefix)
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# SERIES keyword - just the word, don't consume the delimiter
|
|
161
|
-
rule(:series_keyword) do
|
|
162
|
-
str("SERIES")
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# Series identifier - SERIES as PRIMARY type (not modifier)
|
|
166
|
-
# Pattern: CSA code [PREFIX] SERIES:year
|
|
167
|
-
# Examples: CSA Z240 MH SERIES:16, CSA Z240 RV SERIES:23, CSA Z245.20 SERIES:22
|
|
168
|
-
rule(:series_identifier) do
|
|
169
|
-
publisher >>
|
|
170
|
-
# Code comes first (can be letter+number or dotted numbers)
|
|
171
|
-
(
|
|
172
|
-
code_pattern |
|
|
173
|
-
# Letter + multi-digit number for Series (e.g., Z240, A220, B139)
|
|
174
|
-
(letter >> match("[0-9]").repeat(2)).as(:code) |
|
|
175
|
-
# Dotted numeric code (e.g., 245.20)
|
|
176
|
-
(match("[0-9]").repeat(1) >> dot >> match("[0-9]").repeat(1)).as(:code)
|
|
177
|
-
) >>
|
|
178
|
-
space >>
|
|
179
|
-
# Optional series prefix (MH, RV, etc.) before SERIES keyword
|
|
180
|
-
(series_prefix >> space).maybe >>
|
|
181
|
-
series_keyword.as(:series_type) >>
|
|
182
|
-
(colon_year | dash_year).maybe >>
|
|
183
|
-
reaffirmation.maybe
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# CEC (Canadian Electrical Code) identifier
|
|
187
|
-
# Pattern: CSA C22.{2,3,4,6} NO. {number}:{year}
|
|
188
|
-
# Examples: CSA C22.2 NO. 286:23, CSA C22.3 NO. 7:20
|
|
189
|
-
# The "NO." notation is a semantic component and must be preserved
|
|
190
|
-
rule(:cec_identifier) do
|
|
191
|
-
publisher >>
|
|
192
|
-
(
|
|
193
|
-
str("C22.2") | str("C22.3") | str("C22.4") | str("C22.6")
|
|
194
|
-
).as(:cec_part) >>
|
|
195
|
-
no_notation >>
|
|
196
|
-
no_number >>
|
|
197
|
-
(colon_year | dash_year).maybe >>
|
|
198
|
-
reaffirmation.maybe
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# ISO/IEC/CISPR/CEI adopted standards pattern
|
|
202
|
-
# Examples:
|
|
203
|
-
# CSA ISO/IEC 9594-2:21
|
|
204
|
-
# CSA ISO/IEC TR 12785-3:15
|
|
205
|
-
# CSA CISPR 16-1-1:18
|
|
206
|
-
# CAN/CSA-ISO 10819:16
|
|
207
|
-
# CAN/CSA-IEC 62443-2-4:17
|
|
208
|
-
# CAN/CSA-CEI/IEC 61000-4-28-01
|
|
209
|
-
rule(:adoption_identifier) do
|
|
210
|
-
publisher.as(:adoption_pub) >>
|
|
211
|
-
(
|
|
212
|
-
# Option 1: ISO/IEC with TR/TS
|
|
213
|
-
(str("ISO/IEC").as(:adoption_org) >> space >>
|
|
214
|
-
(str("TR") | str("TS")).as(:iso_type) >> space >>
|
|
215
|
-
match("[0-9-]").repeat(1).as(:adoption_number)) |
|
|
216
|
-
# Option 1a: ISO/IEC without TR/TS (must come after TR/TS option in priority)
|
|
217
|
-
(str("ISO/IEC").as(:adoption_org_with_type) >> space >>
|
|
218
|
-
match("[0-9-]").repeat(1).as(:adoption_number)) |
|
|
219
|
-
# Option 2: ISO alone
|
|
220
|
-
(str("ISO").as(:adoption_org) >> space >>
|
|
221
|
-
match("[0-9-]").repeat(1).as(:adoption_number)) |
|
|
222
|
-
# Option 3: IEC alone
|
|
223
|
-
(str("IEC").as(:adoption_org) >> space >>
|
|
224
|
-
match("[0-9-]").repeat(1).as(:adoption_number)) |
|
|
225
|
-
# Option 4: CEI/IEC (French name for IEC)
|
|
226
|
-
(str("CEI/IEC").as(:adoption_org) >> space >>
|
|
227
|
-
match("[0-9-]").repeat(1).as(:adoption_number)) |
|
|
228
|
-
# Option 5: CISPR
|
|
229
|
-
(str("CISPR").as(:adoption_org) >> space >>
|
|
230
|
-
match("[0-9-]").repeat(1).as(:adoption_number))
|
|
231
|
-
) >>
|
|
232
|
-
(colon | dash).as(:adoption_year_sep) >>
|
|
233
|
-
(year_4digit | year_2digit).as(:adoption_year) >>
|
|
234
|
-
(
|
|
235
|
-
# Amendment with year: /A1:22 or /A1-22
|
|
236
|
-
slash >> str("A") >> digit.as(:adoption_amendment) >>
|
|
237
|
-
(colon | dash) >> (year_4digit | year_2digit).as(:adoption_amendment_year)
|
|
238
|
-
).maybe >>
|
|
239
|
-
reaffirmation.maybe
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
# Legacy: ISO/IEC TR/TS only pattern (for backward compatibility)
|
|
243
|
-
rule(:iso_iec_adoption) do
|
|
244
|
-
adoption_identifier
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
# Basic CSA identifier
|
|
248
|
-
rule(:csa_code) do
|
|
249
|
-
publisher >>
|
|
250
|
-
code_pattern >>
|
|
251
|
-
(
|
|
252
|
-
# Option 1: NO. notation (e.g., C22.2 NO. 1:20)
|
|
253
|
-
(no_notation >> no_number >> (colon_year | dash_year)) |
|
|
254
|
-
# Option 2: series with prefix (space + prefix + space + keyword + year)
|
|
255
|
-
(space >> series_prefix >> space >> series_keyword.as(:series) >> (colon_year | dash_year)) |
|
|
256
|
-
# Option 3: series without prefix (space + keyword + year)
|
|
257
|
-
(space >> series_keyword.as(:series) >> (colon_year | dash_year)) |
|
|
258
|
-
# Option 4: just year (no series, no NO.)
|
|
259
|
-
(colon_year | dash_year)
|
|
260
|
-
).maybe >> # trailing year optional (partial reference)
|
|
261
|
-
amendment_slash.maybe # Add amendment support here
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
# Base CSA code without amendment (for combined identifiers)
|
|
265
|
-
rule(:base_csa_code) do
|
|
266
|
-
publisher >>
|
|
267
|
-
code_pattern >>
|
|
268
|
-
(
|
|
269
|
-
# Option 1: NO. notation
|
|
270
|
-
(no_notation >> no_number >> (colon_year | dash_year)) |
|
|
271
|
-
# Option 2: series with prefix
|
|
272
|
-
(space >> series_prefix >> space >> series_keyword.as(:series) >> (colon_year | dash_year)) |
|
|
273
|
-
# Option 3: series without prefix
|
|
274
|
-
(space >> series_keyword.as(:series) >> (colon_year | dash_year)) |
|
|
275
|
-
# Option 4: just year
|
|
276
|
-
(colon_year | dash_year)
|
|
277
|
-
).maybe # trailing year optional (partial reference)
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
# Continuation code (optional CSA publisher prefix, for combined identifiers)
|
|
281
|
-
rule(:continuation_code) do
|
|
282
|
-
publisher.as(:has_publisher).maybe >>
|
|
283
|
-
code_pattern >>
|
|
284
|
-
(
|
|
285
|
-
# Option 1: NO. notation
|
|
286
|
-
(no_notation >> no_number >> (colon_year | dash_year)) |
|
|
287
|
-
# Option 2: series with prefix
|
|
288
|
-
(space >> series_prefix >> space >> series_keyword.as(:series) >> (colon_year | dash_year)) |
|
|
289
|
-
# Option 3: series without prefix
|
|
290
|
-
(space >> series_keyword.as(:series) >> (colon_year | dash_year)) |
|
|
291
|
-
# Option 4: just year
|
|
292
|
-
(colon_year | dash_year)
|
|
293
|
-
).maybe >> # trailing year optional (partial reference)
|
|
294
|
-
amendment_slash.maybe
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
# Bundled portion - code that follows + (base is implied, no CSA prefix)
|
|
298
|
-
# Example: "A2:22" in "CSA C22.2 NO. 60601-1:14 + A2:22"
|
|
299
|
-
rule(:bundled_portion) do
|
|
300
|
-
code_pattern >>
|
|
301
|
-
(
|
|
302
|
-
# Option 1: NO. notation
|
|
303
|
-
(no_notation >> no_number >> (colon_year | dash_year).maybe) |
|
|
304
|
-
# Option 2: just year or no year
|
|
305
|
-
(colon_year | dash_year).maybe
|
|
306
|
-
)
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
# Bundled identifier with + notation (consolidated documents)
|
|
310
|
-
rule(:bundled_identifier) do
|
|
311
|
-
csa_code.as(:base) >>
|
|
312
|
-
plus >>
|
|
313
|
-
bundled_portion.as(:bundled_first) >>
|
|
314
|
-
(plus >> bundled_portion).repeat.as(:bundled_rest) >>
|
|
315
|
-
reaffirmation.maybe
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
# Combined CSA standards with slash
|
|
319
|
-
rule(:combined_slash) do
|
|
320
|
-
base_csa_code.as(:first) >>
|
|
321
|
-
slash >>
|
|
322
|
-
continuation_code.as(:second) >>
|
|
323
|
-
(slash >> continuation_code.as(:third)).maybe >>
|
|
324
|
-
reaffirmation.maybe >>
|
|
325
|
-
package_portion.maybe
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# Combined with comma (CSA B149.1:25, CSA B149.2:25)
|
|
329
|
-
rule(:combined_comma) do
|
|
330
|
-
csa_code.as(:first) >>
|
|
331
|
-
comma >> space >>
|
|
332
|
-
csa_code.as(:second) >>
|
|
333
|
-
reaffirmation.maybe >>
|
|
334
|
-
(
|
|
335
|
-
(space >> ampersand >> package_portion) |
|
|
336
|
-
package_portion
|
|
337
|
-
).as(:package_portion).maybe >>
|
|
338
|
-
str("").as(:comma_separator) # Mark as comma-based
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
# Single identifier with optional package
|
|
342
|
-
rule(:single_identifier) do
|
|
343
|
-
csa_code >> reaffirmation.maybe >> package_portion.as(:package_portion).maybe
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
# Code-only identifier (no CSA prefix) - try this last
|
|
347
|
-
rule(:code_only_identifier) do
|
|
348
|
-
code_pattern >>
|
|
349
|
-
(
|
|
350
|
-
# Option 1: series with prefix
|
|
351
|
-
(space >> series_prefix >> space >> series_keyword.as(:series) >> (colon_year | dash_year)) |
|
|
352
|
-
# Option 2: series without prefix
|
|
353
|
-
(space >> series_keyword.as(:series) >> (colon_year | dash_year)) |
|
|
354
|
-
# Option 3: just year
|
|
355
|
-
(colon_year | dash_year)
|
|
356
|
-
).maybe >> # trailing year optional (partial reference)
|
|
357
|
-
(space >> str("PACKAGE")).as(:package_portion).maybe
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
# Main identifier
|
|
361
|
-
rule(:identifier) do
|
|
362
|
-
iso_iec_adoption | series_identifier | cec_identifier | bundled_identifier | combined_slash | combined_comma | single_identifier | code_only_identifier
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
root(:identifier)
|
|
366
|
-
|
|
367
|
-
# Preprocessing to normalize input
|
|
368
|
-
def parse(input)
|
|
369
|
-
prefix = self.class.publisher_prefix_for(input)
|
|
370
|
-
result = super(self.class.normalize_input(input))
|
|
371
|
-
if prefix && result.is_a?(Hash)
|
|
372
|
-
self.class.inject_publisher_prefix(result, prefix)
|
|
373
|
-
end
|
|
374
|
-
result
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
# Pre-parse ingestion normalizations (R2): every parse path —
|
|
378
|
-
# parslet and PG artifact alike — feeds the grammar the same
|
|
379
|
-
# normalized string.
|
|
5
|
+
class Parser
|
|
6
|
+
# Pre-parse ingestion normalizations (R2): every parse path feeds
|
|
7
|
+
# the grammar the same normalized string.
|
|
380
8
|
def self.normalize_input(input)
|
|
381
9
|
# Skip comment lines
|
|
382
10
|
if input.strip.start_with?("#")
|
|
@@ -435,30 +63,21 @@ module Pubid
|
|
|
435
63
|
private
|
|
436
64
|
|
|
437
65
|
def self.inject_publisher_prefix(hash, prefix)
|
|
438
|
-
#
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
# For bundled identifiers
|
|
453
|
-
if hash[:base]
|
|
454
|
-
inject_publisher_prefix(hash[:base], prefix)
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
# For single identifiers (no :first, no :base, just raw attributes)
|
|
458
|
-
if !hash[:first] && !hash[:base] && !hash[:bundled_first]
|
|
459
|
-
hash[:publisher_prefix] = prefix
|
|
66
|
+
# Combined and bundled identifiers carry their parts under the
|
|
67
|
+
# csa_code_part[] group; the first part always takes the prefix,
|
|
68
|
+
# later ones only when they spell their own publisher token (a
|
|
69
|
+
# slash continuation like "CSA X/60601-1-6" does not).
|
|
70
|
+
if (parts = hash[:csa_code_part])
|
|
71
|
+
parts.each_with_index do |part, index|
|
|
72
|
+
next if index.positive? && !part[:part_has_publisher] &&
|
|
73
|
+
!hash[:comma_separator]
|
|
74
|
+
|
|
75
|
+
part[:publisher_prefix] = prefix
|
|
76
|
+
end
|
|
77
|
+
return hash
|
|
460
78
|
end
|
|
461
79
|
|
|
80
|
+
hash[:publisher_prefix] = prefix
|
|
462
81
|
hash
|
|
463
82
|
end
|
|
464
83
|
end
|
data/lib/pubid/doi/parser.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "parslet"
|
|
4
|
-
|
|
5
3
|
module Pubid
|
|
6
4
|
module Doi
|
|
7
|
-
#
|
|
5
|
+
# Grammar for Digital Object Identifiers per ISO 26324.
|
|
8
6
|
#
|
|
9
7
|
# Accepts:
|
|
10
8
|
# [doi:|DOI:]10.{REGISTRAR}/{SUFFIX}
|
|
@@ -13,40 +11,7 @@ module Pubid
|
|
|
13
11
|
# SUFFIX is one-or-more chars from [A-Za-z0-9._\-/()] (printable minus
|
|
14
12
|
# whitespace). The grammar is intentionally permissive on suffix chars
|
|
15
13
|
# because real DOIs cover the full printable range.
|
|
16
|
-
class Parser
|
|
17
|
-
rule(:dot) { str(".") }
|
|
18
|
-
rule(:slash) { str("/") }
|
|
19
|
-
|
|
20
|
-
# Optional URL resolver prefix.
|
|
21
|
-
rule(:url_prefix) { (str("https://") | str("http://")).maybe >> str("doi.org").maybe }
|
|
22
|
-
|
|
23
|
-
# Optional DOI scheme prefix.
|
|
24
|
-
rule(:scheme_prefix) do
|
|
25
|
-
(str("doi:") | str("DOI:") | str("Doi:")).maybe
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Prefix: "10." then 2+ registrar digits.
|
|
29
|
-
rule(:prefix) { str("10") >> dot >> match("[0-9]").repeat(2).as(:prefix) }
|
|
30
|
-
|
|
31
|
-
# Suffix: at least one printable non-whitespace char (DOI Handbook §2.2).
|
|
32
|
-
rule(:suffix) { match("[A-Za-z0-9._\\-/()]").repeat(1).as(:suffix) }
|
|
33
|
-
|
|
34
|
-
rule(:identifier) do
|
|
35
|
-
url_prefix.maybe >>
|
|
36
|
-
scheme_prefix.maybe >>
|
|
37
|
-
slash.maybe >>
|
|
38
|
-
prefix >> slash >> suffix
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
rule(:root) { identifier }
|
|
42
|
-
|
|
43
|
-
def self.parse(input)
|
|
44
|
-
# Normalize a URL resolver form to bare form before parsing. The
|
|
45
|
-
# parser's `url_prefix.maybe >> scheme_prefix.maybe >> slash.maybe`
|
|
46
|
-
# chain also handles this in-grammar, but pre-stripping yields a
|
|
47
|
-
# cleaner error message for malformed URLs.
|
|
48
|
-
new.parse(input)
|
|
49
|
-
end
|
|
14
|
+
class Parser
|
|
50
15
|
end
|
|
51
16
|
end
|
|
52
17
|
end
|
data/lib/pubid/easc/builder.rb
CHANGED
data/lib/pubid/easc/parser.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "parslet"
|
|
4
|
-
|
|
5
3
|
module Pubid
|
|
6
4
|
module Easc
|
|
7
|
-
#
|
|
5
|
+
# Grammar for EASC identifiers.
|
|
8
6
|
#
|
|
9
7
|
# Accepts the Cyrillic surface forms observed in mgscatalog.by:
|
|
10
8
|
#
|
|
@@ -16,73 +14,7 @@ module Pubid
|
|
|
16
14
|
# Also accepts Latin transliterations (PMG, RMG, V) for
|
|
17
15
|
# round-tripping with English-language tooling. The renderer
|
|
18
16
|
# emits Cyrillic as the canonical form.
|
|
19
|
-
class Parser
|
|
20
|
-
include ::Pubid::Parser::CommonParseRules
|
|
21
|
-
|
|
22
|
-
root :identifier
|
|
23
|
-
|
|
24
|
-
rule(:space) { str(" ") }
|
|
25
|
-
rule(:space?) { space.maybe }
|
|
26
|
-
rule(:dot) { str(".") }
|
|
27
|
-
|
|
28
|
-
# Year separator: ASCII hyphen OR Unicode em-dash / en-dash,
|
|
29
|
-
# optionally surrounded by whitespace.
|
|
30
|
-
rule(:year_sep) do
|
|
31
|
-
space?.maybe >> (str("-") | str("—") | str("–")) >> space?.maybe
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Series prefix. Captured to route to the right Identifiers::*
|
|
35
|
-
# subclass (Pmg vs Rmg). Both Cyrillic and Latin forms.
|
|
36
|
-
rule(:series_word) do
|
|
37
|
-
(str("ПМГ") | str("РМГ") | str("PMG") | str("RMG")).as(:series) >> space
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Defense variant marker — Cyrillic "В" or Latin "V".
|
|
41
|
-
rule(:variant_word) do
|
|
42
|
-
(str("В") | str("V")).as(:variant) >> space
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
rule(:prefix) do
|
|
46
|
-
series_word >> variant_word.maybe
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
rule(:digits) do
|
|
50
|
-
match("[0-9]").repeat(1)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
rule(:number) do
|
|
54
|
-
(digits >> (dot >> digits).repeat).as(:number)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
rule(:year_2digit) do
|
|
58
|
-
match("[0-9]").repeat(2, 2)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
rule(:year_4digit) do
|
|
62
|
-
match("[0-9]").repeat(4, 4)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
rule(:year) do
|
|
66
|
-
(year_4digit | year_2digit).as(:year)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
rule(:identifier) do
|
|
70
|
-
prefix >> number >> (year_sep >> year).maybe
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def self.parse(string)
|
|
74
|
-
unless string.is_a?(String)
|
|
75
|
-
raise ::Pubid::Errors::InvalidInputError,
|
|
76
|
-
::Pubid::INPUT_NOT_A_STRING_MESSAGE
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
if string.length > ::Pubid::MAX_INPUT_LENGTH
|
|
80
|
-
raise ::Pubid::Errors::InvalidInputError,
|
|
81
|
-
::Pubid::INPUT_TOO_LONG_MESSAGE
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
new.parse(string.strip)
|
|
85
|
-
end
|
|
17
|
+
class Parser
|
|
86
18
|
end
|
|
87
19
|
end
|
|
88
20
|
end
|
data/lib/pubid/ecma/CLAUDE.md
CHANGED
|
@@ -6,7 +6,7 @@ These notes were part of the root `CLAUDE.md`. Read them before you change `lib/
|
|
|
6
6
|
|
|
7
7
|
- **ECMA edition and volume — the one flavor whose `to_s` deliberately renders MORE than the document number**: `Relaton::Index::Type#add_or_update` keys on a **bare** `id.to_s` and cannot pass render options, so the default rendering **is** the index key. ECMA's renderer used to say outright that "the edition is never rendered", and 740 of the 804 published `relaton-data-ecma` rows carry one — so all 804 rows collapsed onto **421** keys and **383 were dropped on every crawl, silently** (`ECMA-74` 22 rows to 1, `ECMA-262` 18 to 1). `edition` and `volume` are therefore rendered **by default**, with `to_s(with_edition: false, with_volume: false)` as the opt-out — the inverse of pubid's usual opt-in, and per-flavor divergence in exactly the way 3GPP (`with_publisher: false`) and W3C (`true`) already diverge. **`volume` is a new `attribute :volume, :string`** (relaton's `:vol:`), needed by exactly four rows: ECMA-269 ed3 vol1-4 share one docidentifier **and one title**, so the volume is the only thing separating those four index rows — it is an index discriminator, not something the document prints about itself. Both are plain `:string`s, so `#exclude`/`#matches?` already treat them as wildcards with no override (`ECMA-269` matches every edition under `ignore: %i[edition volume]`); ECMA's `partial_ref_spec.rb` entry moved from `omits: []` to `%i[edition volume]`.
|
|
8
8
|
**Grammar (`lib/pubid/ecma/parser.rb`)**: ` ed<N>` (dotted — `ed5.1` is real, ECMA-402) and ` vol<N>` attach as two independent `.maybe`s **after** the `tr | mem | standard` alternation, so a technical report and a memento carry them exactly as a standard does. The `standard` branch also gained a **space separator** (`ECMA 6`), which is a **NORMALIZING parse** — it renders back as `ECMA-6`, so it lives in `spec/pubid/ecma/edition_volume_spec.rb` and must **never** enter the byte-exact `pass` fixtures (the BIPM `CIPM/2005-06(REV)` precedent). **Two PEG facts are load-bearing and easy to lose.** (1) `standard` cannot shadow ` TR/`/` MEM/` because after the space it demands a **digit** — verified by inverting the alternation order, which still parses both correctly. Keeping `tr | mem` first is belt-and-braces; the **disjoint first character** is the real invariant. (2) Appending a sequence moved the alternation **out of tail position**, so Parslet no longer threads `consume_all` into it and **will not backtrack into an alternative that already succeeded** (`rule(:alt) { str("ab") | str("a") }; rule(:root) { alt >> str("bc") }` fails on `"abc"`). A future branch matching a proper prefix of another branch's input would now fail the whole parse where before it was rescued. `maybe` is `repeat(0,1)` and does not backtrack either, so a marker without its number (` ed`, ` ed5.`, ` vol`) fails atomically and then fails `consume_all` — which is what rejects `ECMA-6 ed` and pins the canonical suffix order (`ECMA-6 vol1 ed3` is rejected).
|
|
9
|
-
**Render-option threading — `:with_volume` rides the `:trademark` route, deliberately.** `Pubid::Identifier#render` `except`s a flag from the rendering context and `slice`s it to the renderer; `:with_edition` was already in both, and `:trademark` is the precedent for an **IEEE-only** flag carried the same way. So `:with_volume` was added to those two lines and **`build_rendering_context`'s signature was left alone** — widening it would touch `Pubid::Iso::SingleIdentifier`'s strict override. The payoff is that **ECMA's `to_s` needed no change at all** and the renderer owns the defaults: no
|
|
9
|
+
**Render-option threading — `:with_volume` rides the `:trademark` route, deliberately.** `Pubid::Identifier#render` `except`s a flag from the rendering context and `slice`s it to the renderer; `:with_edition` was already in both, and `:trademark` is the precedent for an **IEEE-only** flag carried the same way. So `:with_volume` was added to those two lines and **`build_rendering_context`'s signature was left alone** — widening it would touch `Pubid::Iso::SingleIdentifier`'s strict override. The payoff is that **ECMA's `to_s` needed no change at all** and the renderer owns the defaults: no transient ivar, so rendering stays stateless and reentrant (the old `@with_publisher` ivar is gone too — see below). Residual blast radius, accepted: passing `with_volume:` to a *non*-ECMA identifier now reaches that flavor's renderer instead of dying in the context builder — `Renderers::HumanReadable` absorbs unknown opts by design, and ISO's three formerly strict renderers (`DirectivesRenderer`/`IwaRenderer`/`SupplementRenderer`) now take `**_opts` as well, because the shared `with_publisher:` flag reaches them.
|
|
10
10
|
**All three identity surfaces, and the two defects found by measuring them.** Per the project rule, edition and volume reach `to_s`, `to_urn` **and** `to_mr_string`. **URN**: labelled segments after the existing `part-<n>` (`urn:ecma:269:ed-3:vol-2`), purely additive — `UrnParser` probes for a labelled prefix (a `shift_labelled` helper using `delete_prefix`, not `sub`), so a URN minted before the segments existed still reads back. The **dot of a decimal edition is KEPT** (`urn:ecma:402:ed-5.1`): `.` is `unreserved` in RFC 8141 and the URN has an inverse, so it must be lossless. **MR**: ECMA defined **no** `mr_*` hooks, so (a) the base `mr_edition` (`edition&.number`) **raised `NoMethodError` on all 740 edition-carrying rows** — the BIPM/ASTM/ASHRAE crash again — and (b) `mr_publisher` was nil (the publisher is the `PUBLISHER` constant, not the inherited attribute) and `mr_type` was nil (the Builder picks a class, it never sets a `typed_stage`), so **`ECMA-101` and `ECMA TR/101` both slugged to `"101"`** and one output filename overwrote the other. Four hooks fix it, sanitising **by charset** (`gsub(/[^a-z0-9]+/, "-")`, the BIPM `mr_slug` precedent) rather than by an escape list. **The MR is deliberately lossy where the URN is not** (`ed5-1` vs `ed-5.1`): `Renderers::MrString` joins **segments** with `.`, so a dot inside one breaks the documented structure, and nothing parses an ECMA MR back (`Parsers::MrString::FLAVOR_MAP` has no ECMA entry). Say so in the comment rather than leaving the asymmetry looking accidental.
|
|
11
11
|
**New shared `mr_volume` slot.** `Renderers::MrString#render_flat` had no volume segment, so it gained one plus a **literal-nil** `mr_volume` hook on `Pubid::Identifier` — literally nil, **not** `volume&.to_s`, because `Pubid::Nist::Identifiers::Base` declares its own `volume` attribute with different MR semantics and a generic reader would silently move NIST's slug. Inert by construction (no flavor defined `mr_volume`, there is no `method_missing` in `lib/pubid`, and NIST overrides `to_mr_string` wholesale), and verified rather than assumed: a `main` baseline replay of `to_mr_string` over all **98,217** identifiers in `spec/fixtures/*/identifiers/pass/*.txt` is **byte-identical**. `render_flat` was rewritten around a frozen `SEGMENTS` list of hook names while it was open, which also cleared a pre-existing ABC offence.
|
|
12
12
|
**The corpus is the gate.** `spec/fixtures/ecma/identifiers/pass/index_corpus.txt` is the complete published index (804 rows) rendered as `"<:id:>[ ed<:ed:>][ vol<:vol:>]"`, generated from `relaton-data-ecma/index-v1.yaml`; `spec/pubid/ecma/corpus_spec.rb` asserts over all of it that every row parses and renders byte-exactly, that `to_s`/`to_urn`/`to_mr_string` each give **804 distinct** keys, that every slug is filename-safe, that the URN and the hash both round-trip, that `from_hash(to_hash) == id` (stronger than hash equality — `#matches?` is built on `==`), and that `root.number` is non-empty. **ECMA has no `identifiers/full/` corpus**, so `rake "validation:classify[ecma]"` cannot run and cannot clobber these files — its fixtures are hand-maintained, unlike every generated flavor. Note ECMA's `fixtures_spec.rb` glob is **correct** (two `..`, lowercase dir) and is not one of the ten dead ones. **Two existing specs asserted the inverted contract** and had to flip (`serialization_spec.rb`'s "omits edition from the printed string"), so a green suite before the code change was the failure signal, not the success one. **relaton note**: this unblocks `relaton__relaton__ecma-consume-index-v2`; `relaton-data-ecma`'s `index-v1` is string-keyed and no ECMA pubid index is published yet, so nothing needs migrating — but the crawl must run after this lands, or the rows key on the collapsed form again. (hand-off: ecma-edition-and-volume.)
|
|
@@ -26,3 +26,10 @@ keeps `#matches?(other, ignore:)`.
|
|
|
26
26
|
before the declaration existed. `edition` and `volume` stay wildcards —
|
|
27
27
|
an omitted edition really does mean "any edition", which is why
|
|
28
28
|
`partial_ref_spec.rb` lists them under `omits`.
|
|
29
|
+
|
|
30
|
+
- **`with_publisher: false` keeps the `ECMA` token.** `ECMA-262` is how ECMA
|
|
31
|
+
cites its own standard; the old `false` form gave `-262` and `TR/101`, which
|
|
32
|
+
are no designation. `#publisher_prefixes` returns `[]`, so the shared
|
|
33
|
+
stripper (root `CLAUDE.md`, the `with_publisher:` bullet) leaves the string
|
|
34
|
+
alone, and the `@with_publisher` ivar and the renderer branch that read it
|
|
35
|
+
were deleted. XSF made the same change for `XEP 0001`.
|
|
@@ -63,21 +63,26 @@ module Pubid
|
|
|
63
63
|
# would otherwise fail serialization type validation.
|
|
64
64
|
PUBLISHER = "ECMA"
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
attr_reader :with_publisher
|
|
68
|
-
|
|
69
|
-
# Basic string representation. Delegates to renderer. `with_publisher:
|
|
70
|
-
# false` drops the ECMA token (e.g. "-411", "TR/84").
|
|
66
|
+
# Basic string representation. Delegates to renderer.
|
|
71
67
|
#
|
|
72
68
|
# The edition and volume suffixes are rendered by DEFAULT and are opted
|
|
73
69
|
# out of with `to_s(with_edition: false, with_volume: false)`. Both flags
|
|
74
70
|
# ride in `opts` to the renderer, which owns their defaults — see
|
|
75
71
|
# Pubid::Ecma::Renderer for why the default is "with".
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
#
|
|
73
|
+
# `with_publisher:` is accepted and has no effect: `ECMA` is part of the
|
|
74
|
+
# designation (`ECMA-262`), not a publisher prefix, so the internal form
|
|
75
|
+
# is the external one (see {#publisher_prefixes}).
|
|
76
|
+
def to_s(**opts)
|
|
78
77
|
render(format: :human, **opts)
|
|
79
78
|
end
|
|
80
79
|
|
|
80
|
+
# `ECMA-262` is how ECMA itself cites the standard; dropping the token
|
|
81
|
+
# leaves `-262` or `262`, which is no designation at all.
|
|
82
|
+
def publisher_prefixes
|
|
83
|
+
[]
|
|
84
|
+
end
|
|
85
|
+
|
|
81
86
|
# Type token that precedes the number ("TR"/"MEM"), or nil for standards.
|
|
82
87
|
# Overridden by each concrete identifier class.
|
|
83
88
|
def type_prefix
|
|
@@ -129,7 +134,7 @@ module Pubid
|
|
|
129
134
|
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
130
135
|
end
|
|
131
136
|
|
|
132
|
-
parsed = Pubid::Parg::
|
|
137
|
+
parsed = Pubid::Parg::Model.bound(:ecma, identifier)
|
|
133
138
|
Builder.build(parsed)
|
|
134
139
|
end
|
|
135
140
|
|