pubid 2.0.1 → 2.0.3

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.
Files changed (216) hide show
  1. checksums.yaml +4 -4
  2. data/data/ieee/update_codes.yaml +2 -2
  3. data/data/parg/adobe.json +1 -1
  4. data/data/parg/aiee.json +1 -1
  5. data/data/parg/aiee.parg +99 -9
  6. data/data/parg/amca.json +1 -1
  7. data/data/parg/ansi.json +1 -1
  8. data/data/parg/api.json +1 -1
  9. data/data/parg/ashrae.json +1 -1
  10. data/data/parg/asme.json +1 -1
  11. data/data/parg/astm.json +1 -1
  12. data/data/parg/bipm.json +1 -1
  13. data/data/parg/bsi.json +1 -1
  14. data/data/parg/ccsds.json +1 -1
  15. data/data/parg/cen_cenelec.json +1 -1
  16. data/data/parg/cie.json +1 -1
  17. data/data/parg/csa.json +1 -1
  18. data/data/parg/ecma.json +1 -1
  19. data/data/parg/etsi.json +1 -1
  20. data/data/parg/evs.json +1 -1
  21. data/data/parg/gost.json +1 -1
  22. data/data/parg/iala.json +1 -1
  23. data/data/parg/iana.json +1 -1
  24. data/data/parg/idf.json +1 -1
  25. data/data/parg/iec.json +1 -1
  26. data/data/parg/ieee.json +1 -1
  27. data/data/parg/iho.json +1 -0
  28. data/data/parg/ire.json +1 -1
  29. data/data/parg/ire.parg +51 -12
  30. data/data/parg/isbn.json +1 -1
  31. data/data/parg/iso.json +1 -1
  32. data/data/parg/iso.parg +751 -0
  33. data/data/parg/itu.json +1 -1
  34. data/data/parg/jcgm.json +1 -1
  35. data/data/parg/jis.json +1 -1
  36. data/data/parg/nesc.json +1 -1
  37. data/data/parg/nesc.parg +97 -12
  38. data/data/parg/nist.json +1 -1
  39. data/data/parg/oiml.json +1 -1
  40. data/data/parg/omg.json +1 -1
  41. data/data/parg/parg.json +1 -1
  42. data/data/parg/plateau.json +1 -1
  43. data/data/parg/tgpp.json +1 -1
  44. data/data/parg/un.json +1 -1
  45. data/lib/pubid/adobe/identifier.rb +1 -1
  46. data/lib/pubid/adobe/parser.rb +2 -113
  47. data/lib/pubid/amca/identifiers/base.rb +1 -1
  48. data/lib/pubid/amca/parser.rb +1 -145
  49. data/lib/pubid/ansi/identifier.rb +1 -1
  50. data/lib/pubid/ansi/parser.rb +1 -82
  51. data/lib/pubid/api/builder.rb +32 -1
  52. data/lib/pubid/api/identifier.rb +1 -1
  53. data/lib/pubid/api/parser.rb +1 -131
  54. data/lib/pubid/ashrae/identifiers/base.rb +1 -1
  55. data/lib/pubid/ashrae/parser.rb +3 -710
  56. data/lib/pubid/asme/builder.rb +6 -4
  57. data/lib/pubid/asme/identifier.rb +1 -2
  58. data/lib/pubid/asme/parser.rb +1 -328
  59. data/lib/pubid/astm/builder.rb +12 -3
  60. data/lib/pubid/astm/identifier.rb +1 -2
  61. data/lib/pubid/astm/parser.rb +1 -227
  62. data/lib/pubid/bipm/identifier.rb +8 -1
  63. data/lib/pubid/bipm/parser.rb +2 -224
  64. data/lib/pubid/bsi/builder.rb +2 -4
  65. data/lib/pubid/bsi/parser.rb +1 -666
  66. data/lib/pubid/bsi/single_identifier.rb +38 -1
  67. data/lib/pubid/calconnect/parser.rb +2 -40
  68. data/lib/pubid/ccsds/identifier.rb +1 -1
  69. data/lib/pubid/ccsds/parser.rb +1 -66
  70. data/lib/pubid/cen_cenelec/identifier.rb +16 -1
  71. data/lib/pubid/cen_cenelec/parser.rb +3 -136
  72. data/lib/pubid/cie/identifier.rb +1 -1
  73. data/lib/pubid/cie/parser.rb +3 -378
  74. data/lib/pubid/conformance.rb +1 -1
  75. data/lib/pubid/csa/builder.rb +41 -18
  76. data/lib/pubid/csa/identifier.rb +12 -4
  77. data/lib/pubid/csa/identifiers/amendment.rb +28 -0
  78. data/lib/pubid/csa/identifiers.rb +1 -0
  79. data/lib/pubid/csa/parser.rb +16 -397
  80. data/lib/pubid/doi/parser.rb +2 -37
  81. data/lib/pubid/easc/builder.rb +1 -1
  82. data/lib/pubid/easc/parser.rb +2 -70
  83. data/lib/pubid/ecma/CLAUDE.md +8 -1
  84. data/lib/pubid/ecma/identifier.rb +13 -8
  85. data/lib/pubid/ecma/parser.rb +2 -54
  86. data/lib/pubid/ecma/renderer.rb +3 -7
  87. data/lib/pubid/errors.rb +18 -13
  88. data/lib/pubid/etsi/builder.rb +8 -5
  89. data/lib/pubid/etsi/identifiers/base.rb +1 -1
  90. data/lib/pubid/etsi/parser.rb +1 -132
  91. data/lib/pubid/evs/identifier.rb +7 -0
  92. data/lib/pubid/evs/parser.rb +1 -8
  93. data/lib/pubid/evs.rb +1 -1
  94. data/lib/pubid/gb/identifier.rb +6 -0
  95. data/lib/pubid/gb/parser.rb +2 -51
  96. data/lib/pubid/gost/identifier.rb +9 -1
  97. data/lib/pubid/gost/parser.rb +4 -81
  98. data/lib/pubid/iala/builder.rb +48 -39
  99. data/lib/pubid/iala/identifier.rb +1 -1
  100. data/lib/pubid/iala/parser.rb +2 -121
  101. data/lib/pubid/iana/identifier.rb +1 -1
  102. data/lib/pubid/iana/parser.rb +2 -24
  103. data/lib/pubid/identifier.rb +78 -1
  104. data/lib/pubid/idf/builder.rb +42 -0
  105. data/lib/pubid/idf/identifier.rb +1 -1
  106. data/lib/pubid/idf/parser.rb +1 -137
  107. data/lib/pubid/idf.rb +1 -2
  108. data/lib/pubid/iec/builder.rb +158 -50
  109. data/lib/pubid/iec/identifier.rb +1 -1
  110. data/lib/pubid/iec/parser.rb +3 -430
  111. data/lib/pubid/ieee/CLAUDE.md +2 -0
  112. data/lib/pubid/ieee/aiee/identifier.rb +2 -1
  113. data/lib/pubid/ieee/aiee.rb +0 -1
  114. data/lib/pubid/ieee/builder.rb +177 -30
  115. data/lib/pubid/ieee/components/code.rb +46 -7
  116. data/lib/pubid/ieee/identifiers/base.rb +5 -2
  117. data/lib/pubid/ieee/identifiers/joint_development.rb +7 -3
  118. data/lib/pubid/ieee/ire/identifier.rb +2 -1
  119. data/lib/pubid/ieee/ire.rb +0 -1
  120. data/lib/pubid/ieee/nesc/builder.rb +1 -1
  121. data/lib/pubid/ieee/nesc.rb +0 -1
  122. data/lib/pubid/ieee/parser.rb +1 -1257
  123. data/lib/pubid/ieee/project_renderer.rb +5 -1
  124. data/lib/pubid/ieee/renderer.rb +4 -2
  125. data/lib/pubid/ieee/urn_generator.rb +4 -1
  126. data/lib/pubid/ietf/identifiers/base.rb +17 -1
  127. data/lib/pubid/ietf/parser.rb +2 -47
  128. data/lib/pubid/ietf/renderer.rb +17 -2
  129. data/lib/pubid/iho/CLAUDE.md +98 -0
  130. data/lib/pubid/iho/builder.rb +4 -1
  131. data/lib/pubid/iho/identifiers/base.rb +28 -2
  132. data/lib/pubid/iho/renderer.rb +8 -0
  133. data/lib/pubid/iho/urn_generator.rb +14 -1
  134. data/lib/pubid/iho/urn_parser.rb +45 -32
  135. data/lib/pubid/iho.rb +0 -1
  136. data/lib/pubid/isbn/identifier.rb +1 -1
  137. data/lib/pubid/isbn/parser.rb +2 -31
  138. data/lib/pubid/iso/CLAUDE.md +66 -0
  139. data/lib/pubid/iso/builder.rb +229 -15
  140. data/lib/pubid/iso/identifier.rb +22 -12
  141. data/lib/pubid/iso/identifiers/directives.rb +6 -3
  142. data/lib/pubid/iso/identifiers/guide.rb +6 -3
  143. data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +6 -3
  144. data/lib/pubid/iso/normalizer.rb +5 -4
  145. data/lib/pubid/iso/parser.rb +1 -539
  146. data/lib/pubid/iso/supplement_identifier.rb +6 -3
  147. data/lib/pubid/iso/urn_parser.rb +13 -8
  148. data/lib/pubid/iso/utilities.rb +4 -5
  149. data/lib/pubid/iso.rb +1 -19
  150. data/lib/pubid/itu/builder.rb +36 -20
  151. data/lib/pubid/itu/identifiers/base.rb +15 -2
  152. data/lib/pubid/itu/parser.rb +1 -775
  153. data/lib/pubid/jcgm/builder.rb +35 -0
  154. data/lib/pubid/jcgm/parser.rb +1 -151
  155. data/lib/pubid/jcgm.rb +1 -2
  156. data/lib/pubid/jis/CLAUDE.md +36 -0
  157. data/lib/pubid/jis/builder.rb +30 -41
  158. data/lib/pubid/jis/identifier.rb +12 -2
  159. data/lib/pubid/jis/identifiers/welding_engineering_standard.rb +35 -0
  160. data/lib/pubid/jis/identifiers.rb +2 -0
  161. data/lib/pubid/jis/parser.rb +1 -128
  162. data/lib/pubid/jis/renderer.rb +1 -1
  163. data/lib/pubid/jis/supplement_identifier.rb +5 -5
  164. data/lib/pubid/jis/urn_parser.rb +2 -1
  165. data/lib/pubid/jis.rb +3 -2
  166. data/lib/pubid/nist/builder.rb +5 -1
  167. data/lib/pubid/nist/caster.rb +13 -3
  168. data/lib/pubid/nist/identifiers/base.rb +37 -2
  169. data/lib/pubid/nist/identifiers/circular.rb +2 -2
  170. data/lib/pubid/nist/identifiers/circular_supplement.rb +2 -2
  171. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +2 -2
  172. data/lib/pubid/nist/identifiers/crpl_report.rb +2 -2
  173. data/lib/pubid/nist/identifiers/handbook.rb +2 -2
  174. data/lib/pubid/nist/identifiers/internal_report.rb +2 -2
  175. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +2 -2
  176. data/lib/pubid/nist/identifiers/monograph.rb +2 -2
  177. data/lib/pubid/nist/identifiers/report.rb +2 -2
  178. data/lib/pubid/nist/parser.rb +19 -775
  179. data/lib/pubid/nist/router.rb +4 -1
  180. data/lib/pubid/nist/supplement_identifier.rb +2 -2
  181. data/lib/pubid/oasis/parser.rb +2 -16
  182. data/lib/pubid/ogc/parser.rb +2 -26
  183. data/lib/pubid/oiml/identifiers/certification_system.rb +7 -0
  184. data/lib/pubid/oiml/identifiers/dual_published.rb +2 -2
  185. data/lib/pubid/oiml/parser.rb +1 -303
  186. data/lib/pubid/oiml.rb +1 -2
  187. data/lib/pubid/omg/identifier.rb +6 -1
  188. data/lib/pubid/omg/parser.rb +2 -76
  189. data/lib/pubid/parser.rb +5 -4
  190. data/lib/pubid/parsers/mr_string.rb +1 -1
  191. data/lib/pubid/plateau/parser.rb +1 -66
  192. data/lib/pubid/plateau.rb +1 -2
  193. data/lib/pubid/renderers/directives_renderer.rb +1 -1
  194. data/lib/pubid/renderers/iwa_renderer.rb +1 -1
  195. data/lib/pubid/renderers/publisher_stripper.rb +82 -0
  196. data/lib/pubid/renderers/supplement_renderer.rb +1 -1
  197. data/lib/pubid/renderers.rb +1 -0
  198. data/lib/pubid/sae/parser.rb +1 -48
  199. data/lib/pubid/tgpp/identifier.rb +1 -1
  200. data/lib/pubid/tgpp/parser.rb +2 -59
  201. data/lib/pubid/un/identifier.rb +1 -1
  202. data/lib/pubid/un/parser.rb +1 -18
  203. data/lib/pubid/version.rb +1 -1
  204. data/lib/pubid/w3c/parser.rb +2 -27
  205. data/lib/pubid/xsf/identifier.rb +10 -6
  206. data/lib/pubid/xsf/parser.rb +3 -31
  207. data/lib/pubid/xsf/renderer.rb +2 -4
  208. data/lib/pubid.rb +8 -9
  209. metadata +9 -23
  210. data/lib/pubid/ieee/aiee/parser.rb +0 -130
  211. data/lib/pubid/ieee/ire/parser.rb +0 -109
  212. data/lib/pubid/ieee/nesc/parser.rb +0 -155
  213. data/lib/pubid/iho/parser.rb +0 -68
  214. data/lib/pubid/parser/common_parse_methods.rb +0 -13
  215. data/lib/pubid/parser/common_parse_rules.rb +0 -56
  216. data/lib/pubid/parser/grammar.rb +0 -96
@@ -1,1261 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "parslet"
4
-
5
3
  module Pubid
6
4
  module Ieee
7
5
  # Parser class for IEEE identifiers
8
6
  # Single Responsibility: Parsing IEEE identifier syntax
9
7
  # Note: IEEE is extremely complex with many edge cases
10
- class Parser < ::Pubid::Parser::Grammar
11
- # Basic building blocks
12
- rule(:space) { str(" ") }
13
- rule(:space?) { space.maybe }
14
- rule(:dash) { str("-") }
15
- rule(:dash?) { dash.maybe }
16
- rule(:dot) { str(".") }
17
- rule(:slash) { str("/") }
18
- rule(:comma) { str(", ") }
19
- rule(:digit) { match("[0-9]") }
20
- rule(:digits) { digit.repeat(1) }
21
- rule(:letter) { match("[A-Za-z]") }
22
- rule(:upper) { match("[A-Z]") }
23
- rule(:lower) { match("[a-z]") }
24
-
25
- # Year pattern (4 digits starting with 19 or 20), optionally followed by letter(s)
26
- # e.g. 2012, 201x, 2010a
27
- rule(:year_digits) do
28
- (str("19") | str("20")) >> digit.repeat(2,
29
- 2) >> lower.repeat(0,
30
- 2) >> digits.absent?
31
- end
32
-
33
- # Month patterns - numeric format (01-12)
34
- rule(:month_numeric) do
35
- (str("0") >> match("[1-9]")) | # 01-09
36
- (str("1") >> match("[0-2]")) # 10-12
37
- end
38
-
39
- # Comprehensive date parsing
40
- # Format 1: "September 2018" or "Sept 2018" (text month + year)
41
- rule(:date_with_month_text) do
42
- month_name.as(:month) >> space >> year_digits.as(:year)
43
- end
44
-
45
- # Format 2: "2018-09" (year-numeric month)
46
- rule(:date_with_month_numeric) do
47
- year_digits.as(:year) >> dash >> month_numeric.as(:month)
48
- end
49
-
50
- # Format 3: Just year "2018"
51
- rule(:date_year_only) do
52
- year_digits.as(:year)
53
- end
54
-
55
- # Combined date rule - longest match first
56
- rule(:date_standalone) do
57
- date_with_month_text | date_with_month_numeric | date_year_only
58
- end
59
-
60
- # Trailing print/reaffirm date "<sep>Month YYYY" — captured under the
61
- # distinct :trailing_month/:trailing_year keys so it never collides with a
62
- # base -YYYY identity year (the builder promotes it only when no base year
63
- # exists). Accepts BOTH a month name ("May 2014") and a month-first numeric
64
- # month ("05 2014"). The two variants are unambiguous: month_name never
65
- # starts with a digit, and month_numeric (01-12) never matches a bare
66
- # 19xx/20xx year — so a bare trailing year still falls through to callers'
67
- # own bare-year clause.
68
- rule(:trailing_month_year) do
69
- # The numeric branch accepts `-` as well as space before the year:
70
- # preprocessing (the `(\d)\s+(\d{4})` gsub) rewrites a trailing
71
- # "05 2014" → "05-2014", so a numeric month reaches the grammar
72
- # dash-joined; the month-name branch escapes that gsub (the char before
73
- # the space is a letter) and stays space-joined.
74
- ((comma | space) >> month_name.as(:trailing_month) >> space >> year_digits.as(:trailing_year)) |
75
- ((comma | space) >> month_numeric.as(:trailing_month) >> (space | dash) >> year_digits.as(:trailing_year))
76
- end
77
-
78
- # Month patterns
79
- rule(:month_name) do
80
- # Period-suffixed abbreviations (longest first)
81
- str("Sept.") | str("Oct.") | str("Nov.") | str("Dec.") |
82
- str("Jan.") | str("Feb.") | str("Mar.") | str("Apr.") |
83
- str("Jun.") | str("Jul.") | str("Aug.") |
84
- # Full month names
85
- str("January") | str("February") | str("March") | str("April") |
86
- str("May") | str("June") | str("July") | str("August") |
87
- str("September") | str("October") | str("November") | str("December") |
88
- # Non-period abbreviations
89
- str("Jan") | str("Feb") | str("Mar") | str("Apr") | str("Jun") |
90
- str("Jul") | str("Aug") | str("Sep") | str("Sept") | str("Oct") | str("Nov") | str("Dec")
91
- end
92
-
93
- # Organizations
94
- rule(:organization) do
95
- str("IEEE") | str("AIEE") | str("ANSI") | str("ASA") |
96
- # ANS (American Nuclear Society) — a third co-publisher on nuclear
97
- # standards ("ANSI/IEEE/ANS 7.4-3-2-1982"). Listed AFTER ANSI so it
98
- # never shadows the longer token.
99
- str("ANS") |
100
- str("IEC") | str("ISO") | str("ASTM") | str("CSA") | str("ASME") |
101
- str("NACE") | str("NSF") | str("ASHRAE") | str("NCTA") | str("AESC") |
102
- str("EIA") | # NEW Session 224: Add EIA support
103
- # Historical / foreign co-publishers seen in relaton-data-ieee
104
- # (bucket 4): AMPP, USAS (both standalone), and the IEEE sub-board /
105
- # partner co-publishers USEMCSC, EAB, MPAI.
106
- str("USEMCSC") | str("AMPP") | str("USAS") | str("EAB") | str("MPAI")
107
- end
108
-
109
- # Complex organization prefixes (Category 5: ANSI Complex)
110
- rule(:complex_org_prefix) do
111
- str("ANSI/IEEE-ANS") | str("ANSI/IEEE") | str("ANSI")
112
- end
113
-
114
- # Characteristic IEEE number patterns (without prefix)
115
- # These patterns are distinctly IEEE even without "IEEE Std" prefix
116
- rule(:characteristic_ieee_number) do
117
- # C37.xxx series (power systems) - C followed by 2 digits, dot, more digits
118
- (str("C") >> digit.repeat(2,
119
- 2) >> dot >> digits >> match("[a-z]").repeat.maybe) |
120
- # 802.xxx series (networking) - 802 followed by dot, digits, optional letter suffix
121
- (str("802") >> dot >> digits >> match("[a-z]").repeat.maybe) |
122
- # P followed by digits (draft projects)
123
- (str("P") >> digits.repeat(1))
124
- end
125
-
126
- rule(:publisher) do
127
- complex_org_prefix.as(:publisher) | organization.as(:publisher)
128
- end
129
-
130
- rule(:copublisher) do
131
- # Three-way copublisher strings (treat as single unit, longest first)
132
- str("/ISO/IEC").as(:copublisher) |
133
- str("/IEC/ISO").as(:copublisher) |
134
- # Two-way copublishers (original pattern)
135
- (slash >> space? >> organization.as(:copublisher))
136
- end
137
-
138
- # Conformance document patterns (/Conformance01-2003, /Conformance02-2014)
139
- # Allow optional space before slash for malformed inputs
140
- rule(:conformance) do
141
- (space? >> slash >> str("Conformance") >> match("[0-9]").repeat(1).as(:conf_number) >> dash >> year_digits.as(:conf_year)).as(:conformance)
142
- end
143
-
144
- # ASHRAE joint publication patterns (/ASHRAE Guideline 21-2012)
145
- # Also handles /ASHRAE 21 without "Guideline"
146
- rule(:ashrae_copub) do
147
- (slash >> str("ASHRAE") >> space >>
148
- (str("Guideline") >> space).maybe >>
149
- digits.as(:ashrae_number) >>
150
- (dash >> year_digits.as(:ashrae_year)).maybe).as(:ashrae_copub)
151
- end
152
-
153
- # IEEE cross-reference patterns (/C62.22.1-1996)
154
- # References another IEEE standard from a specific series (e.g., C62, C37, C57)
155
- rule(:ieee_crossref) do
156
- (slash >> str("C") >> digits >> dot >> digits >> dot >> digits >> dash >> year_digits).as(:ieee_crossref)
157
- end
158
-
159
- # IPCEA co-designation suffix (/IPCEA P-46-426-1962). Captured verbatim
160
- # (leading slash included) so it round-trips through the `crossref`
161
- # attribute. Used only by the S-designation rule below.
162
- rule(:ipcea_copub) do
163
- (slash >> str("IPCEA") >> space >>
164
- match('[A-Za-z0-9.\-]').repeat(1)).as(:ipcea_copub)
165
- end
166
-
167
- # Historical IEEE/IPCEA co-published cable designation (e.g. S-135).
168
- # The "S-<digits>" number has a dash between the letter series and the
169
- # digits, which the shared `number` rule deliberately rejects (that
170
- # tightening is what keeps a bare "IEEE S" from parsing). So this one-off
171
- # family gets its own rule rather than loosening `number` and risking the
172
- # 12k-row corpus. Requiring `str("S") >> dash >> digits` keeps "IEEE S"
173
- # (no dash+digit) rejected. Handles: "IEEE Std S-135", "IEEE S-135",
174
- # bare "S-135", the "/IPCEA …" slash co-designation, and the
175
- # "(IPCEA …)" parenthetical variant.
176
- rule(:s_designation) do
177
- (publisher >> space).maybe >>
178
- (type_word.as(:type) >> space?).maybe >>
179
- (str("S") >> dash >> digits).as(:s_number) >>
180
- ipcea_copub.maybe >>
181
- parenthetical.maybe
182
- end
183
-
184
- # Document number - support letters and digits, with optional prefix P
185
- # Complex multi-part numbers like P11073-10404-10419 should be fully captured
186
- # But simple cases like "623-1976" should not consume the dash before year
187
- rule(:number) do
188
- (str("P").maybe >>
189
- # The numeric core must contain at least one digit: an optional letter
190
- # prefix (C, S, …) then a required `digits` run, then any digit/letter
191
- # tail. The old `(digits | upper).repeat(1)` wrongly accepted an
192
- # all-letter token like a bare "S" (a prefix with no number — e.g.
193
- # "IEEE S"), which is not a valid IEEE identifier.
194
- upper.repeat(0) >> digits >> (digits | upper).repeat(0) >>
195
- # Only consume dash+digits if followed by another dash+digits (multi-part pattern)
196
- # OR if the digits don't look like a year (not 4 digits starting with 19/20)
197
- # This prevents consuming "623-1976" as a number but allows "P11073-10404-10419"
198
- (dash >> digits >> year_digits.absent? >> (dash >> digits).repeat).maybe >>
199
- lower.maybe).as(:number)
200
- end
201
-
202
- # Type - handle "No." and "No" (case-insensitive), longest first
203
- rule(:type_word) do
204
- str("Draft Std") | str("STD") | str("Standard") |
205
- str("Std No.") | str("Std") | # Add "Std No." before "Std"
206
- str("PTC") | # ASME Performance Test Code
207
- (match("[Nn]") >> str("o.")) | (match("[Nn]") >> str("o")) |
208
- str("No")
209
- end
210
-
211
- # Part and subpart - handle both dot and dash separators
212
- rule(:part) do
213
- (dot | dash) >> match("[0-9A-Za-z]").repeat(1).as(:part)
214
- end
215
-
216
- rule(:subpart) do
217
- (dot | dash | str("_")) >>
218
- ((str("REV") | str("Rev")).maybe >> match("[0-9a-z]").repeat(1) >>
219
- (dot >> digits).maybe).as(:subpart)
220
- end
221
-
222
- # Year component - updated to use comprehensive date parsing
223
- rule(:year) do
224
- (dot | dash) >> date_standalone >> str("(E)").maybe
225
- end
226
-
227
- # Draft patterns
228
- rule(:draft_status) do
229
- # "Active" (bucket 3) joins the generic draft-status path so
230
- # "IEEE Active Std P… /D…" parses like the "Unapproved" forms, without
231
- # touching ieee_approved_draft_identifier (which would break the
232
- # issue-#209 unapproved-drops-Std rendering). "Active Approved" completes
233
- # the two-word matrix alongside "Active Unapproved" (draft-grammar
234
- # coverage: the "<status> Draft P…/D…" prefix forms). Longest token first.
235
- (str("Active Unapproved") | str("Active Approved") | str("Unapproved") |
236
- str("Approved") | str("Active")) >> space
237
- end
238
-
239
- rule(:draft_prefix) do
240
- space? >> (str("/") | str("_") | dash | space)
241
- end
242
-
243
- rule(:draft_version) do
244
- # Enhanced to handle multiple draft notation patterns
245
- # D is optional to handle /08 style drafts (e.g., IEEE P1052/08)
246
- # A draft never begins with "R-" — that is the revision suffix
247
- # (revision_suffix rule); guard so the D-less path doesn't swallow a
248
- # bare "/R-<id>" (e.g. the no-draft "P1722/R-1") as a draft.
249
- (str("R") >> dash).absent? >>
250
- (str("D") >> str("IS").absent?).maybe >> # Avoid matching "DIS" (ISO stage)
251
- (
252
- # Pattern: D3.1 (decimal with 1-2 digits on each side) - MOST COMMON, put first
253
- # Also handles trailing letter: D7.3A, D2.0E
254
- (match("[0-9]").repeat(1,
255
- 2) >> dot >> match("[0-9]").repeat(1,
256
- 2) >> lower.maybe) |
257
- # Pattern: D.XX (decimal starting with dot) - e.g., D.19
258
- (dot >> digits) |
259
- # Pattern: DX+X (plus sign) - e.g., D1+1
260
- (digits >> str("+") >> digits) |
261
- # Pattern: DXXXXeYY or DXXXX.eYY (complex) - e.g., D2012.e27
262
- (digits >> dot.maybe >> str("e") >> digits) |
263
- # Pattern: D-X or DX or DX-d or DX_letter (original patterns)
264
- # Handles: D12, D3.0, D043Rev18, suffixes like D15Sept
265
- (str("-").maybe >> match("[0-9A-Za-z]").repeat(1) >> (str("-d") | (str("_") >> match("[0-9A-Za-z]").repeat(0))).maybe)
266
- ).as(:draft_version)
267
- end
268
-
269
- rule(:draft_date) do
270
- # Enhanced to handle: ", Sept 2008" or " Sept 2008" or ", Month Year"
271
- ((comma | space) >> month_name.as(:month) >> space >> year_digits.as(:year)) |
272
- # Space-separated bare year after the draft designator
273
- # ("IEEE Draft Std P14764/D1 2004, Nov 2004" - the "D1 2004" draft
274
- # carries its own year, with the print date still trailing).
275
- (space >> year_digits.as(:year)) |
276
- # Numeric-month form ", 05 2007" / " 05 2007" — no text month name. The
277
- # year separator may be a dash: preprocessing (parser.rb ~1278) rewrites
278
- # a trailing " <digits> <year>" to "<digits>-<year>", so "05 2007"
279
- # reaches the grammar as "05-2007".
280
- ((comma | space) >> month_numeric.as(:month) >> (space | dash) >> year_digits.as(:year)) |
281
- (((space? >> comma >> space?) | space) >> month_name.as(:month) >>
282
- (
283
- # "Month DD, Year" (day then comma) and "Month DD Year" (day, no
284
- # comma) — the day/year separator may be a dash for the same
285
- # preprocessing reason ("July 15 2012" → "July 15-2012").
286
- ((space >> digits.as(:day)) >> ((comma | space | dash) >> year_digits.as(:year))) |
287
- ((space >> digits.as(:day)).maybe >> comma >> year_digits.as(:year)) |
288
- (comma >> space? >> year_digits.as(:year)) |
289
- (space >> year_digits.as(:year))
290
- ))
291
- end
292
-
293
- # FDIS and similar ISO stage codes without D prefix (Pattern 3)
294
- # These appear after / in IEEE P identifiers but don't have the D prefix
295
- # Examples: IEEE P15939/FDIS, IEEE P1234/CDV
296
- rule(:fdraft) do
297
- (slash >>
298
- (str("FDIS") | str("CDV") | str("CD") | str("WD") | str("PWI") | str("NP")) >>
299
- (
300
- ((comma | space) >> month_name.as(:month) >> space >> year_digits.as(:year)) | # Month Year
301
- ((comma | space) >> year_digits.as(:year)) # Year only (e.g., /FDIS, 2016)
302
- ).maybe >>
303
- parenthetical.maybe).as(:fdraft)
304
- end
305
-
306
- rule(:draft) do
307
- (draft_prefix >> draft_version.repeat(1, 2) >>
308
- (dot >> digits.as(:revision)).maybe >>
309
- # The compound both-systems form (docs/IEEE-DRAFT-STAGES.md
310
- # §1.3): "=DDIS.3" - IEEE draft ordinal = draft of the ISO/IEC
311
- # stage, with its iteration. The glued "=DDIS3" / "=DDIS-3"
312
- # spellings are accepted aliases of "=DDIS.3".
313
- (str("=") >> (str("D") >> (str("FDIS") | str("CDV") | str("PWI") |
314
- str("DIS") | str("WD") | str("NP") | str("CD")) |
315
- (str("FDIS") | str("CDV") | str("PWI") | str("DIS") | str("WD") |
316
- str("NP") | str("CD"))).as(:draft_iso_stage) >>
317
- (dot | dash).maybe >> digits.as(:draft_iso_iteration).maybe).maybe >>
318
- draft_date.maybe).as(:draft)
319
- end
320
-
321
- # Trailing revision suffix "/R-<id>". Normalization funnels every revision
322
- # spelling here — IEEE's native inline "Rev<n>" (repositioned) and
323
- # relaton's synthetic "/R-<x>" (kept in place) — so a single trailing rule
324
- # captures them all. The id is alphanumeric ("2", "18", "i").
325
- rule(:revision_suffix) do
326
- slash >> str("R") >> dash >> match('[0-9A-Za-z]').repeat(1).as(:revision)
327
- end
328
-
329
- # Edition - enhanced to support IEC formats like "Edition 1.0 2015-03"
330
- #
331
- # The edition's own year is captured as :edition_year (NOT :year). The
332
- # generic Std branch carries a base "-YYYY" slot AND a separate trailing
333
- # `edition.maybe`; a bare :year here would collide with the base :year
334
- # when both fire, so Parslet warns "Duplicate subtrees … keys: [:year]"
335
- # and drops the base identity year (the same failure #299 fixed for the
336
- # trailing month/year clause). The builder promotes :edition_year to the
337
- # identity year only when there is no base year.
338
- rule(:edition) do
339
- (comma >> year_digits.as(:edition_year) >> str(" Edition")) |
340
- ((space | dash) >> str("Edition ") >>
341
- (digits >> dot >> digits).as(:edition) >>
342
- # Year separator: a space, " - ", or a bare dash (preprocessing
343
- # rewrites "Edition 3.0 2016" -> "Edition 3.0-2016", the shape the
344
- # normalized "/E-<n>-YYYY" suffix produces — nil-residue item 1).
345
- (str(" - ") | space | dash) >>
346
- year_digits.as(:edition_year) >>
347
- (dash >> digit.repeat(2, 2).as(:edition_month)).maybe) # Capture -MM as edition_month
348
- end
349
-
350
- # Part/subpart/year combinations
351
- rule(:part_subpart_year) do
352
- (part >> subpart.repeat(1, 2) >> year) |
353
- (part >> subpart >> year) |
354
- (part >> year) |
355
- (part >> subpart) |
356
- year |
357
- part
358
- end
359
-
360
- # Corrigendum
361
- rule(:corrigendum) do
362
- # Enhanced: Accept space as separator, make separators more flexible
363
- # Also accept "Corrigendum" as alternative to "Cor"
364
- ((str("_") | slash | dash | space) >>
365
- (str("Corrigendum") | str("Cor")) >>
366
- (dash | dot | space).maybe >> # More flexible separator after "Cor"
367
- space? >> # Add space handling after separator
368
- digits.as(:cor_number).maybe >>
369
- ((dash | str(":") | space) >> year_digits.as(:cor_year)).maybe).as(:corrigendum)
370
- end
371
-
372
- # Amendment — mirrors the corrigendum rule's separator flexibility so
373
- # IEEE-format "/Amd 2-2004" and "/Amd2-2004" parse the same way as
374
- # their Cor counterparts (issue #210).
375
- rule(:amendment) do
376
- ((str("_") | slash | dash | space) >>
377
- (str("Amendment") | str("Amd")) >>
378
- (dash | dot | space).maybe >>
379
- space? >>
380
- digits.as(:amd_number).maybe >>
381
- ((dash | str(":") | space) >> year_digits.as(:amd_year)).maybe).as(:amendment)
382
- end
383
-
384
- # Interpretation notation (/INT)
385
- # Enhanced to support optional year suffix: /INT-1991, /INT 1991
386
- rule(:interpretation) do
387
- (slash >> str("INT") >> ((dash | str(":") | space) >> year_digits.as(:int_year)).maybe).as(:interpretation)
388
- end
389
-
390
- # Reaffirmed - enhanced to support (R1992) format without space
391
- rule(:reaffirmed) do
392
- (
393
- # Format: "Reaffirmed 1992"
394
- (str("Reaffirmed ") >> year_digits.as(:year)) |
395
- # Format: "(R1992)" - parentheses with R prefix (with or without space before)
396
- (space.maybe >> str("(R") >> year_digits.as(:year) >> str(")"))
397
- ).as(:reaffirmed)
398
- end
399
-
400
- # Redline suffix at the very end. Accepts relaton's canonical " Redline"
401
- # (space, no dash) and pubid's older " - Redline" (space-dash-space),
402
- # case-insensitive. Captured (presence only) so the builder sets a
403
- # `redline: true` flag the renderer restores — a redline is a distinct
404
- # document and must not collapse to its base standard.
405
- rule(:redline) do
406
- (space >> (dash >> space).maybe >>
407
- (str("Redline") | str("REDLINE") | str("redline"))).as(:redline)
408
- end
409
-
410
- # Book nickname (e.g., "[The Orange Book]", "[IEEE Gold Book]")
411
- rule(:book_nickname) do
412
- space >> str("[") >> match("[^\\]]").repeat(1).as(:nickname) >> str("]")
413
- end
414
-
415
- # Relationship type keywords for Pattern 4 identifiers
416
- rule(:relationship_revision_of) do
417
- str("Revision of ") | str("Revison of ")
418
- end
419
- rule(:relationship_amendment_to) { str("Amendment to ") }
420
- rule(:relationship_corrigendum_to) do
421
- str("Corrigendum to ") | str("Corrigenda to ")
422
- end
423
- rule(:relationship_incorporates) do
424
- str("incorporates ") | str("Incorporating ") | str("Incorporates ")
425
- end
426
- rule(:relationship_adoption_of) { str("Adoption of ") }
427
- rule(:relationship_supplement_to) { str("Supplement to ") }
428
- rule(:relationship_draft_amendment) do
429
- str("Draft Amendment to ") | str("DRAFT Amendment to ")
430
- end
431
- rule(:relationship_draft_revision) { str("Draft Revision of ") }
432
- rule(:relationship_reaffirmation) { str("Reaffirmation of ") }
433
- rule(:relationship_redesignation) do
434
- str("Redesignation of ") | str("redesignated as ")
435
- end
436
- rule(:relationship_supersedes) { str("Supersedes ") | str("Supercedes ") }
437
- rule(:relationship_previously_designated) do
438
- str("Previously designated as ")
439
- end
440
- rule(:relationship_includes) { str("Includes ") } # NEW Session 171
441
-
442
- # Combined relationship type (longest match first)
443
- rule(:relationship_type) do
444
- relationship_draft_amendment.as(:draft_amendment_to) |
445
- relationship_draft_revision.as(:draft_revision_of) |
446
- relationship_previously_designated.as(:previously_designated_as) |
447
- relationship_reaffirmation.as(:reaffirmation_of) |
448
- relationship_redesignation.as(:redesignation_of) |
449
- relationship_supersedes.as(:supersedes) |
450
- relationship_includes.as(:includes) | # NEW Session 171
451
- relationship_revision_of.as(:revision_of) |
452
- relationship_amendment_to.as(:amendment_to) |
453
- relationship_corrigendum_to.as(:corrigendum_to) |
454
- relationship_incorporates.as(:incorporates) |
455
- relationship_adoption_of.as(:adoption_of) |
456
- relationship_supplement_to.as(:supplement_to)
457
- end
458
-
459
- # Identifier string (for parsing list of related identifiers)
460
- # Captures text until delimiter: comma, closing paren, "and", " / ", "; ", "as amended by"
461
- # Uses absent? to ensure we stop at these delimiters
462
- rule(:identifier_string) do
463
- (
464
- str(", and ").absent? >>
465
- str(" and ").absent? >>
466
- str(", ").absent? >>
467
- str(" as amended by ").absent? >>
468
- # Stop at any "/", ";", or "-" that introduces another relationship
469
- # (look-ahead: separator + relationship_type keyword). This lets the
470
- # slash inside "IEEE Std 525-2007/Cor 1-2015" stay part of the
471
- # identifier, while the slash before "Incorporates ..." splits.
472
- relationship_break.absent? >>
473
- str(")").absent? >>
474
- match(".")
475
- ).repeat(1)
476
- end
477
-
478
- # Identifier list (comma and "and" separated)
479
- rule(:identifier_list) do
480
- identifier_string.as(:id) >>
481
- (
482
- (str(", and ") | str(" and ") | str(", ")) >>
483
- identifier_string.as(:id)
484
- ).repeat
485
- end
486
-
487
- # "as amended by" clause with identifier list
488
- rule(:as_amended_by_clause) do
489
- # Variant 1: "as amended by IEEE's X, Y, Z"
490
- (str(" as amended by IEEE's ") >> identifier_list.as(:amendments)) |
491
- # Variant 2: "as amended by X, Y, Z" (standard)
492
- (str(" as amended by ") >> identifier_list.as(:amendments)) |
493
- # Variant 3: "and its approved amendments" (no specific list)
494
- str(" and its approved amendments").as(:approved_amendments)
495
- end
496
-
497
- # A character sequence that may separate two relationships inside the
498
- # parenthetical: a "/", ";", or "-" with optional surrounding spaces.
499
- # Standalone it is permissive — the actual decision to split is gated by
500
- # `relationship_break`, which requires another relationship_type to
501
- # follow. This way the slash in "IEEE Std 525-2007/Cor 1-2015" stays
502
- # part of the related identifier.
503
- rule(:relationship_separator) do
504
- (space.maybe >> str("/") >> space.maybe) |
505
- (space.maybe >> str(";") >> space.maybe) |
506
- (space.maybe >> str("-") >> space.maybe)
507
- end
508
-
509
- # Look-ahead: a separator followed by another relationship_type keyword.
510
- # Used as an absent? guard in identifier_string so the parser stops
511
- # consuming characters right before a new relationship begins.
512
- rule(:relationship_break) do
513
- relationship_separator >> space.maybe >> relationship_type
514
- end
515
-
516
- # Relationship clause (handles all relationship types)
517
- rule(:relationship_clause) do
518
- space.maybe >> str("(") >>
519
- relationship_type.as(:relationship_type) >>
520
- identifier_list.as(:related_ids) >>
521
- as_amended_by_clause.maybe >>
522
- # Additional relationships separated by "/", ";", or "-" (optionally
523
- # surrounded by spaces). The separator is only honored when followed
524
- # by another relationship_type — identifier-internal slashes like
525
- # "/Cor 1-2015" are not mistaken for relationship breaks because
526
- # identifier_string stops at relationship_break ahead.
527
- (
528
- relationship_separator >>
529
- relationship_type.as(:relationship_type) >>
530
- identifier_list.as(:related_ids) >>
531
- as_amended_by_clause.maybe
532
- ).repeat.as(:additional_rels) >>
533
- str(")")
534
- end
535
-
536
- # Title portion separated by colon (Category 8)
537
- rule(:title_portion) do
538
- str(":") >> space >> match('[^\n]').repeat(1).as(:title)
539
- end
540
-
541
- # Approved Draft suffix (Category 7)
542
- rule(:approved_draft_suffix) do
543
- (space >> str("- (Approved Draft)")) | (space >> str("(Approved Draft)"))
544
- end
545
-
546
- # Additional parameters (inside parentheses)
547
- rule(:additional_parameters) do
548
- (space.maybe >> str("(") >> space.maybe >> # Space before/after '(' optional
549
- # ("( Revision of …)" - a crawl space inside the paren must not push
550
- # the narrative to the catch-all and into the render).
551
- (reaffirmed |
552
- # Handle "Revision of IEEE Std ..." with optional space after Std
553
- (str("Revision of IEEE Std ") >> space.maybe >> match("[^)]").repeat(1).as(:revision_of)) |
554
- # Handle typo "Revison of IEEE Std ..." with optional space after Std
555
- (str("Revison of IEEE Std ") >> space.maybe >> match("[^)]").repeat(1).as(:revision_of)) |
556
- # Handle "Revision to IEEE Std ..." with optional space after Std
557
- (str("Revision to IEEE Std ") >> space.maybe >> match("[^)]").repeat(1).as(:revision_of)) |
558
- # Handle "Revison to IEEE Std ..." with optional space after Std
559
- (str("Revison to IEEE Std ") >> space.maybe >> match("[^)]").repeat(1).as(:revision_of)) |
560
- # Amendment patterns (case-insensitive DRAFT)
561
- ((str("DRAFT") | str("Draft") | str("draft")) >> str(" Amendment to ") >> match("[^)]").repeat(1).as(:draft_amendment_to)) |
562
- (str("Amendment to IEEE Std ") >> space.maybe >> match("[^)]").repeat(1).as(:amendment_to)) |
563
- # Adoption patterns
564
- (str("Adoption of ") >> match("[^)]").repeat(1).as(:adoption)) |
565
- # Other specific patterns
566
- (str("Notebooks") >> space? >> match("[^,\\)]").repeat(1).as(:notebooks)) |
567
- (str("Standard Newspaper(s)") >> space? >> match("[^,\\)]").repeat(1).as(:standard_newspapers)) |
568
- # Catch-all for any other parenthetical content (MUST BE LAST)
569
- match("[^)]").repeat(1).as(:parenthetical_content)
570
- ) >>
571
- str(")").maybe).as(:parameters)
572
- end
573
-
574
- # Parenthetical - try relationship_clause first, then fall back to additional_parameters
575
- rule(:parenthetical) do
576
- relationship_clause | additional_parameters
577
- end
578
-
579
- # IEC/IEEE copublished pattern - handle all variations comprehensively
580
- # BUT exclude P prefix patterns (those are joint development)
581
- rule(:iec_ieee_copublished) do
582
- str("IEC/IEEE") >>
583
- space >>
584
- str("P").absent? >> # NOT a P prefix (would be joint development)
585
- # The copublished number must contain at least one digit. This rejects
586
- # an all-letter placeholder like "IEC/IEEE TR" (no real document
587
- # number) — the same "require a digit" tightening the Standard number
588
- # rule got, applied to the copublished number grammar.
589
- (match("[^0-9\n]").repeat >> digit).present? >>
590
- match("[^\n]").repeat(1).as(:content)
591
- end
592
-
593
- # Joint development patterns (ISO/IEC/IEEE in either IEEE or ISO format)
594
- rule(:joint_development_ieee_format) do
595
- # ISO/IEC/IEEE P26511/D8-2018 or ISO/IEEE P1003.1-2008 or IEC/IEEE P62582-1-2011
596
- # ALSO handle: IEC/IEEE P60780-323, CDV1 2014 (comma before stage code)
597
- # ALSO handle: IEEE/CSA P844.1/293.1/D2 (CSA dual numbering)
598
- # ALSO handle: IEEE/ISO/IEC P42010/D=CD-2020 — the printed publisher
599
- # order is the organization's perspective (pubid#469) and parses as
600
- # printed; it is no longer rewritten to ISO-first.
601
- (str("ISO/IEC/IEEE") | str("IEEE/ISO/IEC") | str("ISO/IEEE") | str("IEC/IEEE") | str("IEEE/CSA")).as(:joint_publishers) >>
602
- space >>
603
- # P = project (the document is a draft): identity-bearing, so it
604
- # is captured and preserved, never silently consumed.
605
- str("P").as(:project_marker) >>
606
- digits.as(:number) >>
607
- ((dot | dash) >> digits.as(:part)).maybe >> # Optional part like .1 or -1
608
- # CSA dual numbering: /293.1 (second number)
609
- (slash >> digits >> (dot >> digits).maybe >> (dash >> digits.as(:draft_version)).maybe).maybe >>
610
- (
611
- # Variant 1b: the ordinal-less stage draft "D=CDV-2020" -
612
- # D (draft) = CDV (the IEC stage it drafts). The year rides in
613
- # the draft clause (a distinct key, so the builder keeps the
614
- # date inside the designator). The year follows IEEE format:
615
- # dash-joined ("D=CD-2020", the ruled canonical); the colon
616
- # spelling ("D=CDV:2020") stays accepted as the alias it was
617
- # frozen with. A dash-year is guarded to 19xx/20xx so a
618
- # YYMM monthcode tail can never be read as a year.
619
- (slash >> str("D") >> str("=") >>
620
- (str("CDV") | str("FDIS") | str("PWI") | str("WD") |
621
- str("NP") | str("DIS") | str("CD")).as(:draft_iso_stage) >>
622
- ((str(":") | str("-")) >>
623
- ((str("19") | str("20")) >> digit >> digit).as(:draft_stage_year)).maybe) |
624
- # Variant 1: /D8 notation (original), with the compound
625
- # both-systems suffix "=DDIS.3" (docs/IEEE-DRAFT-STAGES.md §1.3)
626
- (slash >> str("D") >> digits.as(:draft_version) >>
627
- (str("=") >> (str("D") >> (str("FDIS") | str("CDV") | str("PWI") |
628
- str("DIS") | str("WD") | str("NP") | str("CD")) |
629
- (str("FDIS") | str("CDV") | str("PWI") | str("DIS") | str("WD") |
630
- str("NP") | str("CD"))).as(:draft_iso_stage) >>
631
- (dot | dash).maybe >> digits.as(:draft_iso_iteration).maybe).maybe) |
632
- # Variant 2: , CDV1 notation (comma before stage code) —
633
- # the stage draft of the named ISO/IEC stage, its iteration
634
- (comma >> (str("CDV") | str("FDIS") | str("CD") | str("DIS")).as(:iec_stage) >> digits.maybe.as(:stage_iteration))
635
- ).maybe >>
636
- # Optional edition, from relaton's "/E-<n>" suffix normalized to
637
- # "Edition <n>.0[ YYYY]" (nil-residue hand-off item 1). The edition
638
- # rule carries its own year, so the year clause below simply doesn't
639
- # fire when an edition is present.
640
- edition.maybe >>
641
- ((dash >> year_digits.as(:year)) | # Either -YEAR
642
- (comma.maybe >> space >> month_name.as(:month) >> space.maybe >> year_digits.as(:year))).maybe >> # Or Month YEAR (with optional comma)
643
- revision_suffix.maybe
644
- end
645
-
646
- rule(:joint_development_iso_format) do
647
- # ISO-led stage designations. Two spellings:
648
- # colon form : "ISO/IEC/IEEE FDIS 26511:2018" (already used)
649
- # corpus form : "ISO/IEC/IEEE FDIS P26515-2018-05" (historical)
650
- # The corpus form adds a leading "P" on the number, a trailing
651
- # "-YYYY[-MM]" date (instead of ":YYYY"), multi-digit committee-draft
652
- # stage codes (CD1..CD4) plus CDV, and a wider set of joint publishers.
653
- # (roadmap items 2/3, phase 1). longest publisher token first.
654
- # The stage is OPTIONAL ONLY for ISO-led joint prefixes - the
655
- # stage-less PUBLISHED form (pubid#317): "ISO/IEC/IEEE 26511:2018",
656
- # "ISO/IEEE 11073-20101:2004(E)". Every other prefix keeps the
657
- # stage REQUIREMENT so this rule cannot steal what belongs to
658
- # iec_ieee_copublished ("IEC/IEEE 60076-2016"), the bare-IEEE
659
- # project rules ("IEEE P802.16/D-3") or the ISO flavor
660
- # ("ISO/IEC <n>:<year>").
661
- iso_led = (str("ISO/IEC/IEEE") | str("IEEE/ISO/IEC") |
662
- str("IEEE/IEC/ISO") | str("ISO/IEEE"))
663
- staged_only = (str("IEC/IEEE") | str("IEEE/IEC") |
664
- str("ISO/IEC") | str("IEEE"))
665
- # ISO stage codes: FDIS, FCD, CDV; DIS/CD with an optional round
666
- # digit (DIS2, CD1..CD4); WD/PWI/NP.
667
- iso_stage = ((str("FDIS") >> digit.maybe) | (str("FCD") >> digit.maybe) |
668
- (str("CDV") >> digit.maybe) |
669
- (str("DIS") >> digit.maybe) |
670
- (str("CD") >> digit.maybe) |
671
- (str("WD") >> digit.maybe) | (str("PWI") >> digit.maybe) |
672
- (str("NP") >> digit.maybe)).as(:iso_stage)
673
- std_noise = (space >> str("Std")).maybe
674
- # The empty-string marker distinguishes the stage-less PUBLISHED
675
- # tree in the builder (absence of :iso_stage alone means IEEE format).
676
- ((iso_led.as(:joint_publishers) >> space >>
677
- (iso_stage >> std_noise >> space |
678
- str("").as(:iso_published) >> space.maybe)) |
679
- (staged_only.as(:joint_publishers) >> space >>
680
- iso_stage >> std_noise >> space)) >>
681
- str("P").as(:project_marker).maybe >> # project marker (P =
682
- # project/draft; its
683
- # presence is identity)
684
- digits.as(:number) >>
685
- # part must not swallow the trailing year (year_digits.absent?). A
686
- # DASH-joined part is tagged (:part_dash) - the catalogue-printed
687
- # joint form spells the part with a dash ("21451-7") where the ISO
688
- # form uses the dot.
689
- ((dot >> year_digits.absent? >> digits.as(:part)) |
690
- (dash >> str("").as(:part_dash) >> year_digits.absent? >>
691
- digits.as(:part))).maybe >>
692
- (
693
- (str(":") >> year_digits.as(:year)) |
694
- # The dash-year (and ", Month YYYY" text date below) spellings are
695
- # the catalogue-PRINTED joint form; tag them (:printed_form) so the
696
- # builder routes to a Standard instead of the ISO joint reference.
697
- (dash >> year_digits.as(:year) >>
698
- (dash >> month_numeric.as(:month)).maybe >>
699
- str("").as(:printed_dash_year)) |
700
- # Trailing text date ", April 2015" / " April 2015" (build_joint_development
701
- # already reads :month/:year). No :year collision — the iso rule has no
702
- # other :year capture (edition uses :edition_year).
703
- ((comma | space) >> month_name.as(:month) >> space >>
704
- year_digits.as(:year) >> str("").as(:printed_month_year))
705
- ).maybe >>
706
- # Optional /D<draft> tail; the historical hyphenated form
707
- # "…/D-3-2017" carries its date inside the draft clause (bucket 5);
708
- # a date-less "/D-4" keeps its hyphen (bucket 7), hence dash.maybe.
709
- # A text date may trail the DRAFT itself (pubid#216:
710
- # "CD P26515/D1, March 2017", "FDIS P15289/D3, 2017") — distinct
711
- # keys (:draft_month/:draft_year), because a plain :month/:year
712
- # here collides with the date clause above and parslet drops the
713
- # subtree with a "Duplicate subtrees" warning.
714
- # The ISO/IEC edition marker may sit between the year and an
715
- # amendment/draft tail: "8802.11:2012 (E)/Amd 1-2014". A distinct
716
- # key - a second :parameters capture here collides with the
717
- # trailing parenthetical slot (parslet drops duplicate subtrees).
718
- (space.maybe >> str("(") >>
719
- match("[^)]").repeat(1).as(:edition_marker) >> str(")")).maybe >>
720
- # The stage-draft clause of the ISO-led print: "/D=WD.5" -
721
- # D (draft) = the ISO/IEC stage it drafts, with its iteration
722
- # (docs/IEEE-DRAFT-STAGES.md §1.2).
723
- ((slash >> str("D") >> str("=") >>
724
- ((str("FDIS") >> digit.maybe) | (str("FCD") >> digit.maybe) |
725
- (str("CDV") >> digit.maybe) |
726
- (str("DIS") >> digit.maybe) |
727
- (str("CD") >> digit.maybe) |
728
- (str("WD") >> digit.maybe) | (str("PWI") >> digit.maybe) |
729
- (str("NP") >> digit.maybe)).as(:draft_iso_stage) >>
730
- (dot >> (digits >> match("[A-Za-z]").repeat(0, 1)).as(:draft_iso_iteration)).maybe) |
731
- (slash >> str("D") >> dash.maybe >>
732
- match('[0-9.]').repeat(1).as(:draft_version) >>
733
- (((comma | space) >> month_name.as(:draft_month) >> space >>
734
- year_digits.as(:draft_year)) |
735
- (comma >> year_digits.as(:draft_year))).maybe).maybe).maybe >>
736
- # Optional amendment tail (pubid#317:
737
- # "8802-11:2012/Amd.1:2014(E)") - the flat tree keys reuse
738
- # build_flat_amendment.
739
- (slash >> str("Amd") >> (dot | space).maybe >>
740
- digits.as(:amd_number) >>
741
- ((str(":") | dash) >> year_digits.as(:amd_year)).maybe).maybe >>
742
- # Optional corrigendum tail, the ISO print's dotted spelling
743
- # (pubid#215): "8802-1AC:2018/Cor.1:2020(E)". The shared
744
- # corrigendum rule accepts the slash/dot/colon separators; the
745
- # flat tree routes through build_flat_corrigendum.
746
- corrigendum.maybe >>
747
- # Optional edition, from relaton's "/E-<n>" suffix normalized to
748
- # "Edition <n>.0[ YYYY]" (nil-residue hand-off item 1).
749
- edition.maybe >>
750
- revision_suffix.maybe >>
751
- # A redline is a distinct document of the joint ISO print
752
- # (pubid#215): "15289:2015(E) - Redline". Same flag semantics as
753
- # the generic rules - the builder sets redline: true.
754
- redline.maybe >>
755
- # Published joint docs print a language marker, after either the
756
- # year or the amendment tail: "9945:2009(E)", "...Amd.1:2014(E)".
757
- # The crawl sometimes spaces it ("…(E), January 2017" family,
758
- # pubid#216), and a relationship parenthetical may follow
759
- # ("…CD2 P15288-2013-09 (Revision of …)").
760
- parenthetical.maybe
761
- end
762
-
763
- # Embedded (stage-LAST) ISO-led designations: the corpus writes the ISO
764
- # stage AFTER the (dotted or dashed) part —
765
- # "ISO/IEC/IEEE 29119.4.FDIS, April 2015" (dot part .4, stage .FDIS)
766
- # "ISO/IEC/IEEE 24748-5.CD3, February 2015" (dash part -5, stage .CD3)
767
- # "IEEE P24748.5.CD3, July 2015" (bare IEEE + P)
768
- # — rather than before the number (joint_development_iso_format). Same
769
- # meaning as the stage-first form: `29119.4`/`29119-4` = "29119 part 4" and
770
- # the trailing `.FDIS` is the ISO stage, NOT a second part. Routes through
771
- # the same build_joint_development (via :joint_publishers/:iso_stage), so the
772
- # stage is modeled correctly and the numeric part stays separate.
773
- # The stage-less ISO/IEC label with a colon year and the ISO/IEC
774
- # edition marker: "ISO/IEC 13210:1994 (E)" - the ISO/IEC portion of a
775
- # double-labeled standard. The (E) parenthetical is REQUIRED: without
776
- # it this spelling is the ISO flavor's own form and must not be
777
- # stolen. Routes through build_joint_development like every ISO-led
778
- # joint reference.
779
- rule(:joint_development_iso_iec_edition) do
780
- str("ISO/IEC").as(:joint_publishers) >> space >>
781
- str("").as(:iso_published) >>
782
- str("P").as(:project_marker).maybe >>
783
- digits.as(:number) >>
784
- # The part may be dot- or dash-joined ("8802-9"); a dash-year
785
- # ("1234-2012") is not a part.
786
- # A distinct key: the iso-format route's :part_dash prints as a
787
- # dot in the joint code (the "P24748.2:2018" convention); only
788
- # the ISO/IEC LABEL keeps its printed dash.
789
- ((dot | (dash >> str("").as(:iec_label_dash) >> year_digits.absent?)) >>
790
- digits.as(:part)).maybe >>
791
- (str(":") >> space.maybe >> year_digits.as(:year)) >>
792
- space.maybe >> parenthetical
793
- end
794
-
795
- rule(:joint_development_embedded_stage) do
796
- # publisher set mirrors joint_development_iso_format (incl. bare IEEE);
797
- # longest token first.
798
- (str("ISO/IEC/IEEE") | str("IEEE/ISO/IEC") | str("IEEE/IEC/ISO") |
799
- str("ISO/IEEE") | str("IEC/IEEE") | str("IEEE/IEC") | str("ISO/IEC") |
800
- str("IEEE")).as(:joint_publishers) >>
801
- space >>
802
- str("P").as(:project_marker).maybe >> # project marker (P =
803
- # project/draft; its
804
- # presence is identity)
805
- digits.as(:number) >>
806
- # optional numeric part (dot or dash); must not swallow a year
807
- ((dot | dash) >> year_digits.absent? >> digits.as(:part)).maybe >>
808
- # the embedded stage, dot-separated, closed vocab (same as iso_stage).
809
- # The (digit|letter) look-ahead keeps it at a token boundary so "CD"
810
- # can't match inside a longer token and a glued no-space date
811
- # (".DISMay2013") is left to fall through.
812
- dot >>
813
- ((str("FDIS") >> digit.maybe) | (str("FCD") >> digit.maybe) |
814
- (str("CDV") >> digit.maybe) |
815
- (str("DIS") >> digit.maybe) |
816
- (str("CD") >> digit.maybe) |
817
- (str("WD") >> digit.maybe) | (str("PWI") >> digit.maybe) |
818
- (str("NP") >> digit.maybe)).as(:iso_stage) >>
819
- (digit | match("[A-Za-z]")).absent? >>
820
- # optional trailing date: :YYYY, -YYYY[-MM], or text "Month YYYY"
821
- (
822
- (str(":") >> year_digits.as(:year)) |
823
- (dash >> year_digits.as(:year) >>
824
- (dash >> month_numeric.as(:month)).maybe) |
825
- ((comma | space) >> month_name.as(:month) >> space >> year_digits.as(:year))
826
- ).maybe
827
- end
828
-
829
- # Number-first pattern: "1873-2015 IEEE Standard..."
830
- rule(:number_first_identifier) do
831
- number >>
832
- (dash >> year_digits.as(:year)).maybe >>
833
- space >>
834
- (publisher >> copublisher.repeat.as(:copublishers)).as(:publishers) >>
835
- space >>
836
- (type_word.as(:type) >> space?).maybe >>
837
- match("[^\n]").repeat(0).as(:title)
838
- end
839
-
840
- # IEEE P pattern (without Std): "IEEE P1003.1..." OR just "P1003.1..." (prefix optional)
841
- # `P` is not consumed here: the `number` rule carries its own optional
842
- # leading P and captures it into :number ("P1003.1"), so Code.parse
843
- # peels it as code.prefix and the project marker survives the parse —
844
- # a bare `str("P")` swallowed it and "P1201/D0.3" came back without
845
- # its P at all (pubid#18's data-loss complaint).
846
- rule(:ieee_p_identifier) do
847
- (str("IEEE").as(:publisher) >> space).maybe >> # Make IEEE prefix optional
848
- number >>
849
- (part_subpart_year | edition).maybe >>
850
- # Pattern for /08 style drafts (digits without D prefix) - MUST come before corrigendum
851
- (slash >> digits.as(:draft_version)).as(:digit_draft).maybe >>
852
- # FDIS and other ISO stage codes without D prefix (Pattern 3)
853
- fdraft.maybe >>
854
- # Trailing "Month YYYY" print/reaffirm date. Captured under distinct
855
- # keys so it never collides with the base -YYYY identity year (a
856
- # collision made Parslet drop the base year and warn "Duplicate
857
- # subtrees … keys: [:year]"). The builder promotes it to the identity
858
- # only when there is no base year.
859
- trailing_month_year.maybe >>
860
- corrigendum.maybe >>
861
- draft.maybe >>
862
- # Revision trails the draft (before any date), matching normalization's
863
- # ".../D<n>/R-<x>" repositioning of "P802.16Rev2/D3 Feb 2008".
864
- revision_suffix.maybe >>
865
- # ALSO accept month/year after draft (some patterns like /DX, Month YEAR)
866
- trailing_month_year.maybe >>
867
- # Trailing corrigendum AFTER the draft+date ("…/D1, Jan 2007/Cor. 1").
868
- # The draft's own draft_date has already consumed the date, leaving
869
- # "/Cor. 1" here; the resulting flat corrigendum+draft tree routes to
870
- # build_flat_corrigendum (disjoint from the pre-draft corrigendum slot,
871
- # which only fires when no draft precedes it).
872
- corrigendum.maybe >>
873
- parenthetical.maybe
874
- end
875
-
876
- # ANSI P pattern: "ANSI PN42.34-D9a, 2015" OR "ANSI P1234/D5"
877
- rule(:ansi_p_identifier) do
878
- str("ANSI").as(:publisher) >> space >>
879
- str("P") >> space.maybe >> # Make space after P optional
880
- number >>
881
- (part_subpart_year | edition).maybe >>
882
- # Trailing "Month YYYY"/bare-year date under distinct keys so they
883
- # never collide with the base -YYYY identity year (see ieee_p_identifier).
884
- trailing_month_year.maybe >>
885
- corrigendum.maybe >>
886
- draft.maybe >>
887
- revision_suffix.maybe >>
888
- # ALSO accept month/year after draft
889
- trailing_month_year.maybe >>
890
- # Accept bare year after draft: ", 2015"
891
- ((comma | space) >> year_digits.as(:trailing_year)).maybe >>
892
- # Trailing corrigendum after the draft+date (parity with ieee_p_identifier).
893
- corrigendum.maybe >>
894
- parenthetical.maybe
895
- end
896
-
897
- # IEEE Draft P pattern: "IEEE Draft P802.11..." OR "Draft P802.11..." (IEEE prefix optional)
898
- # An optional status phrase may precede "Draft" — "IEEE Unapproved Draft P…",
899
- # "IEEE Active Approved Draft P…" — the largest index-v2 draft bucket. The
900
- # status is captured (draft_status ends in a space, so "Approved Draft"
901
- # splits cleanly) and round-trips as the draft_status attribute; the literal
902
- # "Draft" stays a bare marker (dropped on render, like the plain form).
903
- # `P` is not consumed here: the `number` rule carries its own optional
904
- # leading P and captures it into :number ("P802.11"), so Code.parse peels
905
- # it as code.prefix and the project marker round-trips (pubid#318) — a
906
- # bare `str("P").maybe` here swallowed it and the render lost the P.
907
- rule(:ieee_draft_p_identifier) do
908
- (str("IEEE").as(:publisher) >> space).maybe >> # Make IEEE prefix optional
909
- draft_status.as(:draft_status).maybe >>
910
- str("Draft") >> space >>
911
- # A status-word draft may carry a bare number
912
- # ("IEEE Unapproved Draft 802.1ah/D4.2"). The `number` rule
913
- # already accepts the bare forms (802.1ah, C57.15, 11073-10471).
914
- number >>
915
- (part_subpart_year | edition).maybe >>
916
- # Trailing "Month YYYY" date under distinct keys so it never collides
917
- # with the base -YYYY identity year (see ieee_p_identifier).
918
- ((space >> month_name.as(:trailing_month) >> space >> year_digits.as(:trailing_year)) |
919
- (space >> month_numeric.as(:trailing_month) >> (space | dash) >> year_digits.as(:trailing_year))).maybe >>
920
- draft.maybe >>
921
- # A print date may trail a draft that carries its own date
922
- # ("IEEE Unapproved Draft 24765/D1 2009, Oct 2009" - the draft's
923
- # year is "2009", the print date is ", Oct 2009").
924
- (comma >> space? >> month_name.as(:trailing_month) >> space >>
925
- year_digits.as(:trailing_year)).maybe >>
926
- revision_suffix.maybe >>
927
- # …and the print date may also trail the repositioned revision
928
- # ("IEEE Unapproved Draft P802.16Rev2/D9a, March 2009" reaches the
929
- # grammar with its inline revision parsed natively).
930
- (comma >> space? >> month_name.as(:trailing_month) >> space >>
931
- year_digits.as(:trailing_year)).maybe >>
932
- # Trailing corrigendum after the draft ("…/D2.0/Cor. 1", or
933
- # "…/D1.0, Dec 2007/Cor. 1" where the draft's own draft_date consumes
934
- # the date, leaving "/Cor. N"). The flat corrigendum+draft tree (no
935
- # :base) routes to build_flat_corrigendum, which rebuilds the base
936
- # standard (carrying the draft + draft_status) and wraps it.
937
- corrigendum.maybe >>
938
- parenthetical.maybe
939
- end
940
-
941
- # IEEE Approved Draft pattern: "IEEE Approved Draft Std P..."
942
- rule(:ieee_approved_draft_identifier) do
943
- str("IEEE").as(:publisher) >>
944
- space >>
945
- str("Approved") >> space >>
946
- (str("Draft Std") | str("Std")).as(:type) >> space >>
947
- str("P").maybe >>
948
- number >>
949
- (part_subpart_year | edition).maybe >>
950
- draft.maybe >>
951
- revision_suffix.maybe >>
952
- parenthetical.maybe
953
- end
954
-
955
- # Combined AIEE identifier pattern: "AIEE No 72-1932 and AIEE No 73-1932"
956
- # Handles "and"-separated AIEE identifiers (from "Nos X and Y" preprocessing)
957
- rule(:combined_aiee_identifier) do
958
- # First AIEE identifier
959
- Aiee::Parser.new.aiee_identifier.as(:first_aiee) >>
960
- # "and" separator
961
- space >> str("and") >> space >>
962
- # Second AIEE identifier
963
- Aiee::Parser.new.aiee_identifier.as(:second_aiee)
964
- end
965
-
966
- # AIEE (American Institute of Electrical Engineers) patterns
967
- # Detect AIEE patterns and delegate to AIEE parser
968
- rule(:aiee_identifier) do
969
- # Lookahead for AIEE patterns - do not consume input
970
- (
971
- # IEEE-AIEE transitional pattern
972
- (str("IEEE-AIEE") >> space >> (str("No.") | str("Nos") | str("No") | str("Standard") | str("Trans."))) |
973
- # A.I.E.E. pattern (with dots, no spaces)
974
- (str("A.I.E.E.") >> space >> (str("No.") | str("Nos") | str("No"))) |
975
- # A. I. E. E. pattern (with dots and spaces)
976
- (str("A. I. E. E.") >> space >> (str("No.") | str("Nos") | str("No") | str("Standard"))) |
977
- # AIEE pattern - extended to include more type words
978
- (str("AIEE") >> space >> (str("No.") | str("Nos") | str("No") | str("Standard") | str("Trans.") | str("Std")))
979
- ).present? >>
980
- # Delegate to AIEE parser if pattern detected
981
- Aiee::Parser.new.aiee_identifier.as(:aiee)
982
- end
983
-
984
- # IRE (Institute of Radio Engineers) patterns
985
- # Detect IRE patterns and delegate to IRE parser
986
- rule(:ire_identifier) do
987
- # Lookahead for IRE patterns - do not consume input
988
- (
989
- # Year-first pattern: "52 IRE 7.S2" or "60 IRE 28 PS7"
990
- ((match("[1-6]") >> digit >> space >> str("IRE")) | # 2-digit year format
991
- (str("19") >> digit.repeat(2, 2) >> space >> str("IRE"))) |
992
- # Prefix-first pattern: "IRE 7.S2-1952" (pubid#316 family 2) —
993
- # IRE then a designation (digits or a type word). Loose on
994
- # purpose: the sub-parser is strict, and a failed delegation just
995
- # falls through to the remaining alternatives.
996
- (str("IRE") >> space >> (digits | str("Standard") | str("Std") | str("Trans"))) |
997
- # IEEE-IRE transitional pattern
998
- (str("IEEE-IRE") >> space)
999
- ).present? >>
1000
- # Delegate to IRE parser if pattern detected
1001
- Ire::Parser.new.ire_identifier.as(:ire)
1002
- end
1003
-
1004
- # NESC (National Electrical Safety Code) patterns
1005
- # Detect NESC patterns and delegate to NESC parser
1006
- rule(:nesc_identifier) do
1007
- # Lookahead for NESC patterns - do not consume input
1008
- (
1009
- # C2-YYYY pattern
1010
- (str("C2-") >> year_digits) |
1011
- # YYYY NESC pattern
1012
- (year_digits >> space >> (str("NESC") | str("National Electrical Safety Code"))) |
1013
- # Draft NESC pattern
1014
- (str("Draft") >> space >> (str("NESC") | str("National Electrical Safety Code"))) |
1015
- # Name-first pattern (NEW)
1016
- (str("National Electrical Safety Code") >> str(",") >> space >> str("C2-")) |
1017
- # Catalogue form: "IEEE Std YYYY NESC ..." / "IEEE Std YYYY National
1018
- # Electrical Safety Code" - the prefix is consumed here (the NESC
1019
- # renderer prepends "IEEE Std" itself) but must not fall through to
1020
- # the generic standard grammar, which would read YYYY as a number.
1021
- (str("IEEE") >> space >> str("Std") >> space >> year_digits >> space >>
1022
- (str("NESC") | str("National Electrical Safety Code")))
1023
- ).present? >>
1024
- # Delegate to NESC parser if pattern detected
1025
- ((str("IEEE") >> space >> str("Std") >> space).maybe >>
1026
- Nesc::Parser.new.nesc_identifier).as(:nesc)
1027
- end
1028
-
1029
- # Draft notation for PSI (e.g., /D2, /D3, rawbib's /D-2)
1030
- rule(:psi_draft) do
1031
- slash >> str("D") >> dash.maybe >> digits.as(:draft_version)
1032
- end
1033
-
1034
- # Date on an SI/PSI identifier: ", Month Year" or "-YEAR[-MM]". The
1035
- # comma branch also accepts a NUMERIC month and a DASH before the
1036
- # year — the renderer emits ", 05 2010" for a numeric-month draft,
1037
- # and the preprocessing gsub rewrites a trailing " <digits> <year>"
1038
- # to "<digits>-<year>" (the same load-bearing coupling draft_date
1039
- # documents), so the re-parse must read its own rendering back.
1040
- rule(:psi_date) do
1041
- (comma >> (month_name | month_numeric).as(:month) >> (space | dash) >>
1042
- year_digits.as(:year)) |
1043
- (dash >> year_digits.as(:year) >>
1044
- (dash >> digit.repeat(2, 2).as(:month)).maybe)
1045
- end
1046
-
1047
- # IEEE/ASTM SI/PSI (Système International) patterns
1048
- # SI = Published metric system standard
1049
- # PSI = Proposed SI (draft)
1050
- # Rawbib spellings (pubid#316 family 3): a bare "IEEE" publisher, a dot
1051
- # separator after the type ("PSI.10", "SI 10.1997"), relaton's
1052
- # hyphenated "/D-<n>" draft, and a dash year-month ("-2010-05").
1053
- rule(:ieee_astm_si_psi) do
1054
- (str("IEEE/ASTM") | str("IEEE")).as(:publishers) >>
1055
- space >>
1056
- (str("PSI") | str("SI")).as(:si_type) >>
1057
- (space | str(".")) >>
1058
- digits.as(:number) >>
1059
- # Glued ".YEAR" edition date ("IEEE/ASTM SI 10.1997")
1060
- (str(".") >> year_digits.as(:year)).maybe >>
1061
- # The date and the draft appear in EITHER order: the legacy
1062
- # spelling puts the draft first ("PSI 10/D2, October 2015"),
1063
- # the rawbib hyphenated form ("PSI 10/D-3-2010") parses in place
1064
- # with its date on the draft clause. Draft-first is tried first so the legacy
1065
- # comma-date keeps its original match; each side is optional so
1066
- # a date-only or draft-less form still parses.
1067
- (
1068
- (psi_draft >> psi_date.maybe) | (psi_date >> psi_draft.maybe)
1069
- ).maybe >>
1070
- # Optional parenthetical (revision relationships)
1071
- parenthetical.maybe
1072
- end
1073
-
1074
- # No-prefix IEEE identifier (characteristic patterns without "IEEE Std")
1075
- # These are patterns that are distinctly IEEE even without explicit publisher
1076
- rule(:no_prefix_ieee) do
1077
- characteristic_ieee_number.as(:number) >>
1078
- # Optional suffix (like -a, -b)
1079
- (dash >> match("[A-Za-z]")).maybe.as(:suffix) >>
1080
- # Optional year
1081
- (dash >> year_digits).maybe.as(:year) >>
1082
- # Optional draft notation
1083
- draft.maybe >>
1084
- # Optional language portion
1085
- (str("(E)") | str("(F)")).maybe >>
1086
- # Optional parenthetical content
1087
- parenthetical.maybe
1088
- end
1089
-
1090
- # Corrigendum identifier with recursive base parsing
1091
- # Captures base identifier for recursive parsing, then corrigendum supplement
1092
- # Example: IEEE Std 535-2013/Cor. 1-2017
1093
- rule(:corrigendum_identifier) do
1094
- # Match a complete base identifier (reuse existing patterns)
1095
- # Try standard patterns that would match "IEEE Std 535-2013"
1096
- (
1097
- ((publisher >> copublisher.repeat.as(:copublishers)).as(:publishers) >> space).maybe >>
1098
- (type_word.as(:type) >> space?).maybe >>
1099
- number >>
1100
- part_subpart_year.maybe # This captures the full identifier before /Cor
1101
- ).as(:base) >>
1102
- # Now match the corrigendum portion
1103
- (slash | dash | space) >>
1104
- str("Cor") >>
1105
- (dash | dot | space).maybe >> # More flexible separator after "Cor"
1106
- space? >>
1107
- digits.as(:cor_number) >>
1108
- ((dash | str(":") | space) >> year_digits.as(:cor_year)).maybe >> # Optional cor year suffix
1109
- parenthetical.maybe
1110
- end
1111
-
1112
- # Interpretation identifier with recursive base parsing
1113
- # Captures base identifier for recursive parsing, then interpretation supplement
1114
- # Example: IEEE Std 1076/INT-1991, IEEE Std 1003.1-1988/INT
1115
- rule(:interpretation_identifier) do
1116
- # Match a complete base identifier
1117
- (
1118
- ((publisher >> copublisher.repeat.as(:copublishers)).as(:publishers) >> space).maybe >>
1119
- (type_word.as(:type) >> space?).maybe >>
1120
- number >>
1121
- part_subpart_year.maybe
1122
- ).as(:base) >>
1123
- # Now match the interpretation portion
1124
- (slash | dash | space) >>
1125
- str("INT") >>
1126
- ((dash | str(":") | space) >> year_digits.as(:int_year)).maybe >> # Optional year suffix
1127
- parenthetical.maybe
1128
- end
1129
-
1130
- # Conformance identifier with recursive base parsing
1131
- # Captures base identifier for recursive parsing, then conformance supplement
1132
- # Example: IEEE Std 802.16/Conformance01-2003
1133
- rule(:conformance_identifier) do
1134
- # Match a complete base identifier
1135
- (
1136
- ((publisher >> copublisher.repeat.as(:copublishers)).as(:publishers) >> space).maybe >>
1137
- (type_word.as(:type) >> space?).maybe >>
1138
- number >>
1139
- part_subpart_year.maybe
1140
- ).as(:base) >>
1141
- # Now match the conformance portion
1142
- (slash | dash | space) >>
1143
- str("Conformance") >>
1144
- match("[0-9]").repeat(1).as(:conf_number) >>
1145
- dash >>
1146
- year_digits.as(:conf_year) >>
1147
- parenthetical.maybe
1148
- end
1149
-
1150
- # Multi-numbered identifier: same document with multiple numbers
1151
- # Examples: IEEE Std 1299/C62.22.1-1996, IEEE Std 960-1989, Std 1177-1989
1152
- rule(:multi_numbered_identifier) do
1153
- # Primary identifier (full IEEE identifier)
1154
- ((
1155
- (publisher >> space).maybe >>
1156
- (type_word.as(:type) >> space?).maybe >>
1157
- number >>
1158
- (part_subpart_year | edition).maybe
1159
- ).as(:primary_identifier) >>
1160
- # Separator: slash for cross-ref format, comma for joint standard
1161
- (slash >> str("C") >> digits >> dot >> digits >> dot >> digits >> dash >> year_digits).as(:secondary_crossref)) |
1162
- (comma >> space >> (type_word.as(:type) >> space?).maybe >> number >> dash >> year_digits).as(:secondary_joint)
1163
- end
1164
-
1165
- # CSA dual published pattern: IEEE Std 844.1-2017/CSA C22.2 No. 293.1-17
1166
- rule(:csa_dual_published) do
1167
- # IEEE portion (full identifier)
1168
- (
1169
- publisher >> space >>
1170
- (type_word.as(:type) >> space?).maybe >>
1171
- number >>
1172
- (part_subpart_year | edition).maybe
1173
- ).as(:ieee_portion) >>
1174
- # CSA portion with slash separator
1175
- slash >>
1176
- str("CSA") >> space >>
1177
- # CSA number formats (various patterns observed)
1178
- (
1179
- # Format 1: C22.2 No. 293.1-17 (with NO., either case)
1180
- (str("C") >> digit.repeat(2) >> dot >> digit >> space >> (str("No") | str("NO")) >> dot >> space >>
1181
- match("[0-9.]").repeat(1) >> (dash | str(":")) >> digit.repeat(2)) |
1182
- # Format 2: C293.2-17 (without NO., dash year)
1183
- (str("C") >> match("[0-9.]").repeat(1) >> dash >> digit.repeat(2)) |
1184
- # Format 3: C22.2 No. 293.3:19 (with NO., either case, colon year)
1185
- (str("C") >> digit.repeat(2) >> dot >> digit >> space >> (str("No") | str("NO")) >> dot >> space >>
1186
- match("[0-9.]").repeat(1) >> str(":") >> digit.repeat(2)) |
1187
- # Format 4: C293.4:19 (without NO., colon year)
1188
- (str("C") >> match("[0-9.]").repeat(1) >> str(":") >> digit.repeat(2))
1189
- ).as(:csa_portion)
1190
- end
1191
-
1192
- # Basic IEEE identifier (no dual PubIDs or complex revisions yet)
1193
- rule(:identifier) do
1194
- combined_aiee_identifier |
1195
- aiee_identifier |
1196
- combined_aiee_identifier |
1197
- ire_identifier |
1198
- nesc_identifier |
1199
- ieee_astm_si_psi | # NEW Session 171: Add IEEE/ASTM SI/PSI support
1200
- multi_numbered_identifier | # NEW: Try multi-numbered identifiers before generic patterns
1201
- csa_dual_published | # NEW: Try CSA dual published before generic patterns
1202
- s_designation | # Historical IEEE/IPCEA cable designation (S-135)
1203
- corrigendum_identifier | # NEW: Try corrigendum before generic patterns
1204
- interpretation_identifier | # NEW: Try interpretation identifier before generic patterns
1205
- conformance_identifier | # NEW: Try conformance identifier before generic patterns
1206
- joint_development_ieee_format |
1207
- joint_development_iso_format |
1208
- joint_development_iso_iec_edition | # stage-less ISO/IEC label with (E) - double-label portion
1209
-
1210
- joint_development_embedded_stage | # stage-LAST embedded form (before generic)
1211
- iec_ieee_copublished |
1212
- number_first_identifier |
1213
- ieee_approved_draft_identifier |
1214
- ieee_draft_p_identifier |
1215
- ieee_p_identifier |
1216
- ansi_p_identifier | # NEW: ANSI P prefix support
1217
- (((publisher >> copublisher.repeat.as(:copublishers)).as(:publishers) >> space).maybe >> # Make publisher optional
1218
- draft_status.as(:draft_status).maybe >>
1219
- (str("Draft Std").as(:type) >> space?).maybe >>
1220
- (type_word.as(:type) >> (space >> str("No") >> space).maybe >> space?).maybe >>
1221
- number >>
1222
- (part_subpart_year | edition).maybe >>
1223
- corrigendum.maybe >>
1224
- amendment.maybe >>
1225
- interpretation.maybe >> # NEW: Add /INT support
1226
- conformance.maybe >> # NEW: Add /Conformance support
1227
- ashrae_copub.maybe >> # NEW: Add /ASHRAE Guideline support
1228
- ieee_crossref.maybe >> # NEW: Add /C62.22.1-1996 cross-reference support
1229
- draft.maybe >>
1230
- revision_suffix.maybe >>
1231
- # Trailing "Month YYYY" print/reaffirm date under distinct keys so it
1232
- # never collides with the base -YYYY identity year (see
1233
- # ieee_p_identifier). The builder promotes it only when no base year.
1234
- trailing_month_year.maybe >>
1235
- # The ASHRAE joint suffix may also trail a draft+date
1236
- # ("IEEE P1635/D13, December, 2017/ASHRAE Guideline 21"), so a
1237
- # second slot catches it after the date.
1238
- ashrae_copub.maybe >>
1239
- # A bracketed narrative may sit between the base and a trailing
1240
- # corrigendum: "IEEE Std 671-1985 [Corrigendum to IEEE Std 671-1985
1241
- # (Reaff 2008)]/Cor. 1-2010" captures the bracket as the base's
1242
- # nickname before the wrapper is parsed.
1243
- book_nickname.maybe >>
1244
- # Trailing corrigendum after the draft+date ("IEEE Approved P1015/D1,
1245
- # Jan 2007/Cor. 1"): the generic bucket is the only path a status-word
1246
- # form reaches. Routes via build_flat_corrigendum (disjoint from the
1247
- # pre-draft corrigendum slot at line 954).
1248
- corrigendum.maybe >>
1249
- edition.maybe >>
1250
- parenthetical.maybe >> # REVERT: Back to single parenthetical
1251
- book_nickname.maybe >> # NEW: Add book nickname support
1252
- redline.maybe >>
1253
- title_portion.maybe >>
1254
- approved_draft_suffix.maybe) |
1255
- no_prefix_ieee # NEW: Try no-prefix patterns last (lowest priority)
1256
- end
1257
-
1258
- root(:identifier)
8
+ class Parser
1259
9
  def self.normalize_input(string)
1260
10
  # Strip .pdf extension if present (Pattern 3: File Extensions)
1261
11
  cleaned = string.sub(/\.pdf$/i, "")
@@ -1269,7 +19,6 @@ module Pubid
1269
19
  # No valid IEEE identifier pattern needs more than 1 space
1270
20
  cleaned = cleaned.gsub(/\s+/, " ")
1271
21
 
1272
-
1273
22
  # NEW Session 171: Remove wrong ! prefix
1274
23
  cleaned = cleaned.gsub(/^!IEEE /, "IEEE ")
1275
24
 
@@ -1647,7 +396,6 @@ module Pubid
1647
396
  # Remove period after "Std": "IEEE Std." -> "IEEE Std"
1648
397
  cleaned = cleaned.gsub(/\bStd\.\s+/, "Std ")
1649
398
 
1650
-
1651
399
  # Title portion removal after year: "YYYY - IEEE Standard for..."
1652
400
  cleaned = cleaned.gsub(
1653
401
  /(\d{4})(\s+\([^)]+\))?\s+-\s+IEEE\s+Standard\s+for.*$/, '\1\2'
@@ -1787,10 +535,6 @@ module Pubid
1787
535
 
1788
536
  cleaned
1789
537
  end
1790
-
1791
- def self.parse(string)
1792
- new.parse(normalize_input(string))
1793
- end
1794
538
  end
1795
539
  end
1796
540
  end