pubid 2.0.0 → 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.
Files changed (214) hide show
  1. checksums.yaml +4 -4
  2. data/data/parg/adobe.json +1 -1
  3. data/data/parg/aiee.json +1 -1
  4. data/data/parg/amca.json +1 -1
  5. data/data/parg/ansi.json +1 -1
  6. data/data/parg/api.json +1 -1
  7. data/data/parg/ashrae.json +1 -1
  8. data/data/parg/asme.json +1 -1
  9. data/data/parg/astm.json +1 -1
  10. data/data/parg/bipm.json +1 -1
  11. data/data/parg/bsi.json +1 -1
  12. data/data/parg/calconnect.json +1 -1
  13. data/data/parg/ccsds.json +1 -1
  14. data/data/parg/cen_cenelec.json +1 -1
  15. data/data/parg/cie.json +1 -1
  16. data/data/parg/csa.json +1 -1
  17. data/data/parg/doi.json +1 -1
  18. data/data/parg/easc.json +1 -1
  19. data/data/parg/ecma.json +1 -1
  20. data/data/parg/etsi.json +1 -1
  21. data/data/parg/evs.json +1 -1
  22. data/data/parg/gb.json +1 -1
  23. data/data/parg/gost.json +1 -1
  24. data/data/parg/iala.json +1 -1
  25. data/data/parg/iana.json +1 -1
  26. data/data/parg/idf.json +1 -1
  27. data/data/parg/iec.json +1 -1
  28. data/data/parg/ieee.json +1 -29571
  29. data/data/parg/ietf.json +1 -1
  30. data/data/parg/iho.json +1 -0
  31. data/data/parg/ire.json +1 -1
  32. data/data/parg/isbn.json +1 -1
  33. data/data/parg/iso.json +1 -8510
  34. data/data/parg/itu.json +1 -1
  35. data/data/parg/jcgm.json +1 -1
  36. data/data/parg/jis.json +1 -1
  37. data/data/parg/nesc.json +1 -1
  38. data/data/parg/nist.json +1 -9588
  39. data/data/parg/oasis.json +1 -1
  40. data/data/parg/ogc.json +1 -1
  41. data/data/parg/oiml.json +1 -1
  42. data/data/parg/omg.json +1 -1
  43. data/data/parg/parg.json +1 -1
  44. data/data/parg/plateau.json +1 -1
  45. data/data/parg/sae.json +1 -1
  46. data/data/parg/tables/typed_stages.yaml +1 -0
  47. data/data/parg/tgpp.json +1 -1
  48. data/data/parg/un.json +1 -1
  49. data/data/parg/w3c.json +1 -1
  50. data/data/parg/xsf.json +1 -1
  51. data/lib/pubid/adobe/identifier.rb +1 -1
  52. data/lib/pubid/adobe/parser.rb +2 -113
  53. data/lib/pubid/amca/identifiers/base.rb +1 -1
  54. data/lib/pubid/amca/parser.rb +1 -145
  55. data/lib/pubid/ansi/identifier.rb +1 -1
  56. data/lib/pubid/ansi/parser.rb +1 -82
  57. data/lib/pubid/api/builder.rb +32 -1
  58. data/lib/pubid/api/identifier.rb +1 -1
  59. data/lib/pubid/api/parser.rb +1 -131
  60. data/lib/pubid/ashrae/identifiers/base.rb +1 -1
  61. data/lib/pubid/ashrae/parser.rb +3 -710
  62. data/lib/pubid/asme/builder.rb +6 -4
  63. data/lib/pubid/asme/identifier.rb +1 -2
  64. data/lib/pubid/asme/parser.rb +1 -328
  65. data/lib/pubid/astm/builder.rb +12 -3
  66. data/lib/pubid/astm/identifier.rb +1 -2
  67. data/lib/pubid/astm/parser.rb +1 -227
  68. data/lib/pubid/bipm/identifier.rb +8 -1
  69. data/lib/pubid/bipm/parser.rb +2 -224
  70. data/lib/pubid/bsi/builder.rb +2 -4
  71. data/lib/pubid/bsi/parser.rb +1 -666
  72. data/lib/pubid/bsi/single_identifier.rb +38 -1
  73. data/lib/pubid/calconnect/identifier.rb +1 -1
  74. data/lib/pubid/calconnect/parser.rb +2 -40
  75. data/lib/pubid/ccsds/identifier.rb +1 -1
  76. data/lib/pubid/ccsds/parser.rb +1 -66
  77. data/lib/pubid/cen_cenelec/identifier.rb +16 -1
  78. data/lib/pubid/cen_cenelec/parser.rb +3 -136
  79. data/lib/pubid/cie/identifier.rb +1 -1
  80. data/lib/pubid/cie/parser.rb +3 -378
  81. data/lib/pubid/conformance.rb +1 -1
  82. data/lib/pubid/csa/builder.rb +41 -18
  83. data/lib/pubid/csa/identifier.rb +12 -4
  84. data/lib/pubid/csa/identifiers/amendment.rb +28 -0
  85. data/lib/pubid/csa/identifiers.rb +1 -0
  86. data/lib/pubid/csa/parser.rb +16 -397
  87. data/lib/pubid/doi/identifier.rb +1 -1
  88. data/lib/pubid/doi/parser.rb +2 -37
  89. data/lib/pubid/easc/builder.rb +1 -1
  90. data/lib/pubid/easc/identifier.rb +1 -1
  91. data/lib/pubid/easc/parser.rb +2 -70
  92. data/lib/pubid/ecma/CLAUDE.md +8 -1
  93. data/lib/pubid/ecma/identifier.rb +13 -8
  94. data/lib/pubid/ecma/parser.rb +2 -54
  95. data/lib/pubid/ecma/renderer.rb +3 -7
  96. data/lib/pubid/errors.rb +18 -13
  97. data/lib/pubid/etsi/builder.rb +8 -5
  98. data/lib/pubid/etsi/identifiers/base.rb +1 -1
  99. data/lib/pubid/etsi/parser.rb +1 -132
  100. data/lib/pubid/evs/identifier.rb +7 -0
  101. data/lib/pubid/evs/parser.rb +1 -8
  102. data/lib/pubid/evs.rb +1 -1
  103. data/lib/pubid/gb/identifier.rb +7 -1
  104. data/lib/pubid/gb/parser.rb +2 -51
  105. data/lib/pubid/gost/identifier.rb +9 -1
  106. data/lib/pubid/gost/parser.rb +4 -81
  107. data/lib/pubid/iala/parser.rb +2 -121
  108. data/lib/pubid/iana/identifier.rb +1 -1
  109. data/lib/pubid/iana/parser.rb +2 -24
  110. data/lib/pubid/identifier.rb +78 -1
  111. data/lib/pubid/idf/builder.rb +42 -0
  112. data/lib/pubid/idf/identifier.rb +1 -1
  113. data/lib/pubid/idf/parser.rb +1 -137
  114. data/lib/pubid/idf.rb +1 -2
  115. data/lib/pubid/iec/builder.rb +158 -50
  116. data/lib/pubid/iec/identifier.rb +1 -1
  117. data/lib/pubid/iec/parser.rb +3 -430
  118. data/lib/pubid/ieee/aiee/identifier.rb +2 -1
  119. data/lib/pubid/ieee/aiee.rb +0 -1
  120. data/lib/pubid/ieee/builder.rb +2 -2
  121. data/lib/pubid/ieee/identifiers/base.rb +1 -1
  122. data/lib/pubid/ieee/ire/identifier.rb +2 -1
  123. data/lib/pubid/ieee/ire.rb +0 -1
  124. data/lib/pubid/ieee/nesc.rb +0 -1
  125. data/lib/pubid/ieee/parser.rb +1 -1257
  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 +89 -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 +11 -1
  134. data/lib/pubid/iho/urn_parser.rb +21 -11
  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/builder.rb +1 -1
  139. data/lib/pubid/iso/identifiers/directives.rb +6 -3
  140. data/lib/pubid/iso/identifiers/guide.rb +6 -3
  141. data/lib/pubid/iso/identifiers/international_standard.rb +3 -1
  142. data/lib/pubid/iso/identifiers/international_workshop_agreement.rb +6 -3
  143. data/lib/pubid/iso/normalizer.rb +1 -1
  144. data/lib/pubid/iso/parser.rb +1 -539
  145. data/lib/pubid/iso/supplement_identifier.rb +6 -3
  146. data/lib/pubid/iso/utilities.rb +4 -5
  147. data/lib/pubid/iso.rb +1 -19
  148. data/lib/pubid/itu/builder.rb +36 -20
  149. data/lib/pubid/itu/identifiers/base.rb +15 -2
  150. data/lib/pubid/itu/parser.rb +1 -775
  151. data/lib/pubid/jcgm/builder.rb +35 -0
  152. data/lib/pubid/jcgm/parser.rb +1 -151
  153. data/lib/pubid/jcgm.rb +1 -2
  154. data/lib/pubid/jis/builder.rb +18 -29
  155. data/lib/pubid/jis/identifier.rb +1 -1
  156. data/lib/pubid/jis/parser.rb +1 -128
  157. data/lib/pubid/nist/builder.rb +5 -1
  158. data/lib/pubid/nist/caster.rb +13 -3
  159. data/lib/pubid/nist/identifiers/base.rb +37 -2
  160. data/lib/pubid/nist/identifiers/circular.rb +2 -2
  161. data/lib/pubid/nist/identifiers/circular_supplement.rb +2 -2
  162. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +2 -2
  163. data/lib/pubid/nist/identifiers/crpl_report.rb +2 -2
  164. data/lib/pubid/nist/identifiers/handbook.rb +2 -2
  165. data/lib/pubid/nist/identifiers/internal_report.rb +2 -2
  166. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +2 -2
  167. data/lib/pubid/nist/identifiers/monograph.rb +2 -2
  168. data/lib/pubid/nist/identifiers/report.rb +2 -2
  169. data/lib/pubid/nist/parser.rb +19 -775
  170. data/lib/pubid/nist/router.rb +4 -1
  171. data/lib/pubid/nist/supplement_identifier.rb +2 -2
  172. data/lib/pubid/oasis/identifier.rb +1 -1
  173. data/lib/pubid/oasis/parser.rb +2 -16
  174. data/lib/pubid/ogc/identifier.rb +1 -1
  175. data/lib/pubid/ogc/parser.rb +2 -26
  176. data/lib/pubid/oiml/identifiers/certification_system.rb +7 -0
  177. data/lib/pubid/oiml/identifiers/dual_published.rb +2 -2
  178. data/lib/pubid/oiml/parser.rb +1 -303
  179. data/lib/pubid/oiml.rb +1 -2
  180. data/lib/pubid/omg/identifier.rb +6 -1
  181. data/lib/pubid/omg/parser.rb +2 -76
  182. data/lib/pubid/parg/artifact.rb +22 -3
  183. data/lib/pubid/parg/model.rb +98 -0
  184. data/lib/pubid/parg.rb +1 -0
  185. data/lib/pubid/parser.rb +5 -4
  186. data/lib/pubid/parsers/mr_string.rb +1 -1
  187. data/lib/pubid/plateau/parser.rb +1 -66
  188. data/lib/pubid/plateau.rb +1 -2
  189. data/lib/pubid/renderers/directives_renderer.rb +1 -1
  190. data/lib/pubid/renderers/iwa_renderer.rb +1 -1
  191. data/lib/pubid/renderers/publisher_stripper.rb +82 -0
  192. data/lib/pubid/renderers/supplement_renderer.rb +1 -1
  193. data/lib/pubid/renderers.rb +1 -0
  194. data/lib/pubid/sae/identifiers/base.rb +1 -1
  195. data/lib/pubid/sae/parser.rb +1 -48
  196. data/lib/pubid/tgpp/identifier.rb +1 -1
  197. data/lib/pubid/tgpp/parser.rb +2 -59
  198. data/lib/pubid/un/identifier.rb +1 -1
  199. data/lib/pubid/un/parser.rb +1 -18
  200. data/lib/pubid/version.rb +1 -1
  201. data/lib/pubid/w3c/identifier.rb +1 -1
  202. data/lib/pubid/w3c/parser.rb +2 -27
  203. data/lib/pubid/xsf/identifier.rb +11 -7
  204. data/lib/pubid/xsf/parser.rb +3 -31
  205. data/lib/pubid/xsf/renderer.rb +2 -4
  206. data/lib/pubid.rb +8 -9
  207. metadata +15 -25
  208. data/lib/pubid/ieee/aiee/parser.rb +0 -130
  209. data/lib/pubid/ieee/ire/parser.rb +0 -109
  210. data/lib/pubid/ieee/nesc/parser.rb +0 -155
  211. data/lib/pubid/iho/parser.rb +0 -68
  212. data/lib/pubid/parser/common_parse_methods.rb +0 -13
  213. data/lib/pubid/parser/common_parse_rules.rb +0 -56
  214. data/lib/pubid/parser/grammar.rb +0 -96
@@ -24,7 +24,18 @@ module Pubid
24
24
  Jcgm.locate_type(typed_stage.type_code)
25
25
  end
26
26
 
27
+ # The bound grammar flattens the old `(base as base)` wrapper (the
28
+ # bindings cannot nest): the base document keeps the canonical
29
+ # number/date captures and the supplement layer captures
30
+ # supp_number/supp_date. Rebuild the nested shape the builder has
31
+ # always consumed — the base fields rename under :base and the
32
+ # supplement layer takes type_with_stage plus its own
33
+ # supp_number/supp_date, which is exactly the tree the
34
+ # amendment/corrigendum rules produced before the flatten.
35
+ SUPPLEMENT_STAGES = %w[Amd Cor Corrigendum].freeze
36
+
27
37
  def build(parsed_hash)
38
+ parsed_hash = unflatten_supplement(parsed_hash)
28
39
  klass = locate_identifier_klass(parsed_hash)
29
40
 
30
41
  # GUM guides store their part number in the shared `number` attribute
@@ -52,6 +63,30 @@ module Pubid
52
63
 
53
64
  private
54
65
 
66
+ def unflatten_supplement(parsed_hash)
67
+ stage = parsed_hash[:type_with_stage]
68
+ return parsed_hash if parsed_hash[:base] || stage.nil? ||
69
+ !SUPPLEMENT_STAGES.include?(stage)
70
+
71
+ base_hash = {}
72
+ %i[publisher number gum_number date languages].each do |key|
73
+ base_hash[key] = parsed_hash.delete(key) if parsed_hash[key]
74
+ end
75
+ stage = parsed_hash.delete(:type_with_stage)
76
+ supplement_hash = { type_with_stage: stage }
77
+ if parsed_hash[:supp_number]
78
+ supplement_hash[:number] = parsed_hash.delete(:supp_number)
79
+ end
80
+ if parsed_hash[:supp_date]
81
+ supplement_hash[:date] = parsed_hash.delete(:supp_date)
82
+ end
83
+ # Keep the base as a raw hash: the shared assign_attributes loop
84
+ # routes :base through cast, which recursively builds it — the
85
+ # same path the tree backend's nested subtree took.
86
+ supplement_hash[:base] = base_hash
87
+ parsed_hash.merge(supplement_hash)
88
+ end
89
+
55
90
  def default_identifier_class
56
91
  Identifiers::Guide
57
92
  end
@@ -1,158 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "parslet"
4
-
5
3
  module Pubid
6
4
  module Jcgm
7
- class Parser < ::Pubid::Parser::Grammar
8
- include ::Pubid::Parser::CommonParseRules
9
- include ::Pubid::Parser::CommonParseMethods
10
-
11
- root :identifier
12
-
13
- rule(:identifier) do
14
- meeting_identifier | amendment_identifier | corrigendum_identifier |
15
- numbered_corrigendum_identifier | base | named_guide_identifier
16
- end
17
-
18
- # "JCGM 200:2008 Corrigendum" — a base guide plus a trailing
19
- # " Corrigendum" word (no number). Must precede base: Parslet
20
- # is an ordered PEG that commits to the first matching alternative, so if
21
- # base ran first it would consume "JCGM 200:2008" and root
22
- # would fail on the leftover " Corrigendum". Emits type_with_stage so the
23
- # builder routes it to Corrigendum.
24
- rule(:corrigendum_identifier) do
25
- base.as(:base) >> space >>
26
- str("Corrigendum").as(:type_with_stage)
27
- end
28
-
29
- # "JCGM 101:2008/Cor 1:2009" — a base guide plus a slash-separated
30
- # numbered corrigendum (mirrors the ISO/IEC supplement form). The
31
- # trailing :YYYY is optional so "JCGM 101:2008/Cor 1" also parses.
32
- # Must precede base for the same PEG-commit reason as corrigendum_identifier.
33
- rule(:numbered_corrigendum_identifier) do
34
- base.as(:base) >>
35
- str("/") >> str("Cor").as(:type_with_stage) >>
36
- supplement_separator >> digits.as(:number) >>
37
- corrigendum_date.maybe
38
- end
39
-
40
- # "Amd 1" and the dotted "Amd.1:2026" BIPM prints on its
41
- # publications page (bipm.org, JCGM 100:2008/Amd.1:2026) are both
42
- # accepted on input; rendering stays "Amd 1".
43
- rule(:supplement_separator) do
44
- space | (str(".") >> space.maybe)
45
- end
46
-
47
- # Dateless "named" guides: "JCGM GUM", "JCGM VIM-3" (and future "VIM-N").
48
- # Last in the alternation — only reached when base fully fails
49
- # (these have no date, so number_portion/date_portion never match).
50
- rule(:named_guide_identifier) do
51
- publisher >> space >> named_number
52
- end
53
-
54
- rule(:named_number) do
55
- (str("GUM") | (str("VIM") >> (str("-") >> digits).maybe)).as(:number)
56
- end
57
-
58
- # Committee/meeting record, e.g. "JCGM 17th Meeting (2012)". Diverges
59
- # from base right after the digits (base wants ":", meeting
60
- # wants the ordinal suffix), so ordered choice is unambiguous. Emits the
61
- # same tokens as a guide plus type_with_stage "Meeting", so the generic
62
- # builder path resolves it to Identifiers::Meeting (like "Amd").
63
- #
64
- # The trailing " (YYYY)" group is optional, so the partial reference
65
- # "JCGM 11st Meeting" parses with `date` left nil. JCGM numbers its
66
- # meetings in one sequence, so the ordinal alone names the event and the
67
- # year is redundant detail. The whole group is `.maybe` (not the year
68
- # inside it), so a dangling "(" or a truncated year still fails —
69
- # the same shape as `date_portion.maybe` in `rule(:base)`.
70
- rule(:meeting_identifier) do
71
- publisher >> space >> digits.as(:number) >> ordinal_suffix >>
72
- space >> str("Meeting").as(:type_with_stage) >>
73
- (space >> str("(") >> year_digits.as(:date) >> str(")")).maybe
74
- end
75
-
76
- rule(:ordinal_suffix) do
77
- str("st") | str("nd") | str("rd") | str("th")
78
- end
79
-
80
- # `date_portion` is optional so a partial reference that omits the
81
- # trailing `:YYYY` date (e.g. "JCGM 100") parses with `date` left nil,
82
- # letting relaton match a collection via `matches?(row, ignore: [:date])`.
83
- # The whole sub-rule is `.maybe` (not the year inside it) so a dangling
84
- # ":" still fails. Mirrors ETSI (lib/pubid/etsi/parser.rb).
85
- rule(:base) do
86
- publisher >> space >> number_portion >>
87
- date_portion.maybe >> language_portion.maybe
88
- end
89
-
90
- rule(:amendment_identifier) do
91
- base.as(:base) >>
92
- str("/") >> amendment_type >>
93
- supplement_separator >> digits.as(:number) >>
94
- amendment_date.maybe
95
- end
96
-
97
- rule(:publisher) do
98
- str("JCGM").as(:publisher)
99
- end
100
-
101
- # Number can be plain (100, 200) or GUM-prefixed (GUM-1, GUM-6)
102
- rule(:number_portion) do
103
- gum_number | standard_number
104
- end
105
-
106
- rule(:standard_number) do
107
- digits.as(:number)
108
- end
109
-
110
- rule(:gum_number) do
111
- str("GUM-") >> digits.as(:gum_number)
112
- end
113
-
114
- # Date can be YYYY or YYYY-MM-DD
115
- rule(:date_portion) do
116
- str(":") >> (full_date | year_only)
117
- end
118
-
119
- rule(:year_only) do
120
- year_digits.as(:date)
121
- end
122
-
123
- rule(:full_date) do
124
- (year_digits >> str("-") >> month_digits >> str("-") >> day_digits).as(:date)
125
- end
126
-
127
- # Language: "(E)", "(F)", "(E/F)", "(F/E)"
128
- rule(:language_portion) do
129
- str("(") >>
130
- (
131
- # Multi-language: E/F, F/E
132
- (str("E/F") | str("F/E")) |
133
- # Single language: E, F, R, etc.
134
- match("[A-Z]")
135
- ).as(:languages) >>
136
- str(")")
137
- end
138
-
139
- # Amendment support
140
- rule(:amendment_type) do
141
- str("Amd").as(:type_with_stage)
142
- end
143
-
144
- rule(:amendment_number) do
145
- digits.as(:number)
146
- end
147
-
148
- rule(:amendment_date) do
149
- str(":") >> (full_date | year_only)
150
- end
151
-
152
- # Numbered corrigendum date: same shape as amendment_date.
153
- rule(:corrigendum_date) do
154
- str(":") >> (full_date | year_only)
155
- end
5
+ class Parser
156
6
  end
157
7
  end
158
8
  end
data/lib/pubid/jcgm.rb CHANGED
@@ -32,10 +32,9 @@ module Pubid
32
32
  raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
33
33
  end
34
34
 
35
- parser = Parser.new
36
35
  builder = Builder.new
37
36
 
38
- parsed = Pubid::Parg::Backend.parse(:jcgm, identifier)
37
+ parsed = Pubid::Parg::Model.bound(:jcgm, identifier)
39
38
  builder.build(parsed)
40
39
  end
41
40
 
@@ -9,12 +9,14 @@ module Pubid
9
9
 
10
10
  def build(data)
11
11
  # Handle supplement case first; the SYMBOL clause (if any) attaches to
12
- # the outermost identifier, be it the base or the supplement.
13
- identifier = if data[:amendment]
12
+ # the outermost identifier, be it the base or the supplement. The
13
+ # bindings bind the supplement's fields flat (amd_type/amd_number/…),
14
+ # so their presence marks the supplement kind.
15
+ identifier = if data[:amd_type]
14
16
  build_amendment(data)
15
- elsif data[:explanation]
17
+ elsif data[:expl_type]
16
18
  build_explanation(data)
17
- elsif data[:corrigendum]
19
+ elsif data[:corr_type]
18
20
  build_corrigendum(data)
19
21
  else
20
22
  build_single_identifier(data)
@@ -58,49 +60,36 @@ module Pubid
58
60
  end
59
61
 
60
62
  def build_amendment(data)
61
- amd_data = data[:amendment]
63
+ # The bindings carry the supplement's fields at the top level; the
64
+ # base document shares them.
65
+ base = build_single_identifier(data)
62
66
 
63
- # Build base document (without supplement)
64
- base_data = data.except(:amendment)
65
- base = build_single_identifier(base_data)
66
-
67
- # Build amendment
68
67
  Identifiers::Amendment.new(
69
68
  base: base,
70
- number: amd_data[:amd_number].to_i,
71
- year: amd_data[:amd_year].to_i,
72
- reaffirmed: (true if amd_data[:amd_reaffirmed]),
69
+ number: data[:amd_number].to_i,
70
+ year: data[:amd_year].to_i,
71
+ reaffirmed: (true if data[:amd_reaffirmed]),
73
72
  )
74
73
  end
75
74
 
76
75
  def build_explanation(data)
77
- expl_data = data[:explanation]
78
-
79
- # Build base document (without supplement)
80
- base_data = data.except(:explanation)
81
- base = build_single_identifier(base_data)
76
+ base = build_single_identifier(data)
82
77
 
83
- # Build explanation
84
78
  Identifiers::Explanation.new(
85
79
  base: base,
86
- number: expl_data[:expl_number]&.to_i,
80
+ number: data[:expl_number]&.to_i,
87
81
  year: base.year,
88
82
  )
89
83
  end
90
84
 
91
85
  def build_corrigendum(data)
92
- corr_data = data[:corrigendum]
93
-
94
- # Build base document (without supplement)
95
- base_data = data.except(:corrigendum)
96
- base = build_single_identifier(base_data)
86
+ base = build_single_identifier(data)
97
87
 
98
- # Build corrigendum
99
88
  Identifiers::Corrigendum.new(
100
89
  base: base,
101
- number: corr_data[:corr_number].to_i,
102
- year: corr_data[:corr_year].to_i,
103
- reaffirmed: (true if corr_data[:corr_reaffirmed]),
90
+ number: data[:corr_number].to_i,
91
+ year: data[:corr_year].to_i,
92
+ reaffirmed: (true if data[:corr_reaffirmed]),
104
93
  )
105
94
  end
106
95
 
@@ -174,7 +174,7 @@ module Pubid
174
174
  raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
175
175
  end
176
176
 
177
- parsed = Pubid::Parg::Backend.parse(:jis, identifier)
177
+ parsed = Pubid::Parg::Model.bound(:jis, identifier)
178
178
  Builder.build(parsed)
179
179
  end
180
180
  end
@@ -1,135 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "parslet"
4
-
5
3
  module Pubid
6
4
  module Jis
7
- class Parser < ::Pubid::Parser::Grammar
8
- # Japanese character normalization
9
- rule(:jp_dash) { str("ー") } # Full-width dash
10
- rule(:jp_space) { str(" ") } # Full-width space
11
- rule(:jp_colon) { str(":") } # Full-width colon
12
- rule(:jp_lparen) { str("(") }
13
- rule(:jp_rparen) { str(")") }
14
-
15
- # Normalized separators
16
- rule(:dash) { (str("-") | jp_dash).as(:dash) }
17
- rule(:space) { str(" ") | jp_space }
18
- rule(:colon) { str(":") | jp_colon }
19
-
20
- # Basic elements
21
- rule(:digit) { match["0-9"] }
22
- rule(:digits) { digit.repeat(1) }
23
- rule(:letter) { match["A-Z"] }
24
-
25
- # JIS prefix (optional, handles both "JIS " and "JIS/")
26
- rule(:jis_prefix) { str("JIS") >> (space | str("/")).maybe }
27
-
28
- # Type prefix (TR or TS, can come with or without slash)
29
- rule(:type_prefix) do
30
- (str("TR") | str("TS")).as(:type) >> (space | str("/"))
31
- end
32
-
33
- # Series letter
34
- rule(:series) { letter.as(:series) }
35
-
36
- # Number (preserve as string to maintain leading zeros)
37
- rule(:number) { digits.as(:number) }
38
-
39
- # Parts (multi-level: -1, -2-1, -3-2-1, etc.)
40
- rule(:part) do
41
- dash >> digits.as(:part)
42
- end
43
-
44
- rule(:parts) { part.repeat(0).as(:parts) }
45
-
46
- # Reaffirmation marker (再確認): trailing "R" on a year
47
- rule(:reaffirmed) { str("R").maybe.as(:reaffirmed) }
48
-
49
- # Year
50
- rule(:year) do
51
- colon >> digits.as(:year) >> reaffirmed
52
- end
53
-
54
- # Language code
55
- rule(:language) do
56
- (str("(") | jp_lparen) >>
57
- match["EJ"].as(:language) >>
58
- (str(")") | jp_rparen)
59
- end
60
-
61
- # All-parts notation (Japanese)
62
- rule(:all_parts) do
63
- ((jp_lparen >> str("規格群") >> jp_rparen) |
64
- (str("(") >> str("規格群") >> str(")"))).as(:all_parts)
65
- end
66
-
67
- # Amendment supplement
68
- rule(:amendment) do
69
- str("/") >>
70
- (str("AMENDMENT") | str("AMD")).as(:amd_type) >>
71
- space >>
72
- digits.as(:amd_number) >>
73
- colon >>
74
- digits.as(:amd_year) >>
75
- str("R").maybe.as(:amd_reaffirmed)
76
- end
77
-
78
- # Explanation supplement
79
- rule(:explanation) do
80
- str("/") >>
81
- (str("EXPLANATION") | str("EXPL")).as(:expl_type) >>
82
- (space >> digits.as(:expl_number)).maybe
83
- end
84
-
85
- # Corrigendum supplement
86
- rule(:corrigendum) do
87
- str("/") >>
88
- (str("CORRIGENDUM") | str("CORR")).as(:corr_type) >>
89
- space >>
90
- digits.as(:corr_number) >>
91
- colon >>
92
- digits.as(:corr_year) >>
93
- str("R").maybe.as(:corr_reaffirmed)
94
- end
95
-
96
- # Supplements (AMD, EXPL or CORRIGENDUM, only one for JIS)
97
- rule(:supplement) do
98
- amendment.as(:amendment) | explanation.as(:explanation) |
99
- corrigendum.as(:corrigendum)
100
- end
101
-
102
- # Graphical-symbol sub-reference (e.g. JIS Z 8210 safety signs):
103
- # "SYMBOL <id>" where <id> runs to the end of the string and may be a
104
- # number, an alphanumeric code or a free-text phrase. The keyword may
105
- # also appear bare with no value.
106
- rule(:symbol_clause) do
107
- space >> str("SYMBOL").as(:symbol_present) >>
108
- (space >> match["^\n"].repeat(1).as(:symbol_value)).maybe
109
- end
110
-
111
- # Main identifier
112
- rule(:identifier) do
113
- jis_prefix.maybe >>
114
- type_prefix.maybe >>
115
- space.maybe >>
116
- series >>
117
- space.maybe >>
118
- number >>
119
- parts >>
120
- year.maybe >>
121
- language.maybe >>
122
- all_parts.maybe >>
123
- supplement.maybe >>
124
- symbol_clause.maybe
125
- end
126
-
127
- rule(:root) { identifier }
128
-
129
- # Parse and normalize Japanese characters
130
- def self.parse(input)
131
- new.parse(input)
132
- end
5
+ class Parser
133
6
  end
134
7
  end
135
8
  end
@@ -85,9 +85,13 @@ module Pubid
85
85
 
86
86
  # NEW: Check for CIRC supplement pattern
87
87
  # Note: :base_portion is lost during parser merge, so check for supplement indicators
88
+ # circ_series routes the bare "NBS.CIRC.sup" form: the edition-2
89
+ # grammar cannot carry the zero-width supplement_empty marker the
90
+ # Parslet tree used, and the series capture marks every circular
91
+ # supplement head.
88
92
  if parsed_hash[:supplement_date_range] || parsed_hash[:supplement_slash_year] ||
89
93
  parsed_hash[:supplement_month_year] || parsed_hash[:supplement_year] ||
90
- parsed_hash[:supplement_empty] ||
94
+ parsed_hash[:supplement_empty] || parsed_hash[:circ_series] ||
91
95
  parsed_hash[:supplement] || parsed_hash[:base_portion]
92
96
  return build_circular_supplement(parsed_hash)
93
97
  end
@@ -128,7 +128,16 @@ module Pubid
128
128
  id: value[:owmwp_year])
129
129
  { first_number: Components::Code.new(value: number_part), edition: edition_part }
130
130
 
131
- when :first_number, :second_number
131
+ # The polymorphic number wrappers split per branch in the grammar
132
+ # (asme's designator/designator_code pattern): first_number /
133
+ # second_number (string) arrive over the bare branches,
134
+ # first_number_composite / second_number_composite (raw) over the
135
+ # branches carrying inner captures. The composite keys map onto
136
+ # the same dispatch.
137
+ when :first_number, :first_number_composite, :second_number,
138
+ :second_number_composite
139
+ type = :first_number if type == :first_number_composite
140
+ type = :second_number if type == :second_number_composite
132
141
  return nil if value.nil? || value.to_s.strip.empty?
133
142
 
134
143
  # NEW: Handle OWMWP date-based number (nested hash structure)
@@ -1039,8 +1048,9 @@ module Pubid
1039
1048
 
1040
1049
  when :edition_month, :edition_year, :edition_day, :edition_has_rev
1041
1050
  # These work together: edition_month + edition_year -> single edition ID
1042
- # Skip processing if this is edition_month alone (will be processed with edition_year)
1043
- return nil if type == :edition_month
1051
+ # Skip processing if this is edition_month or edition_day alone
1052
+ # (both are consumed by the edition_year branch via parsed_hash)
1053
+ return nil if type == :edition_month || type == :edition_day
1044
1054
 
1045
1055
  # Process edition_year, combining with edition_month if present
1046
1056
  return nil if value.nil? || value.to_s.strip.empty?
@@ -346,7 +346,7 @@ module Pubid
346
346
  # @param format [:full, :long, :abbreviated, :short, :mr] output format
347
347
  def to_s(format = nil)
348
348
  # Handle both keyword argument (hash) and positional argument (symbol/string)
349
- annotated = format.is_a?(Hash) ? format[:annotated] : nil
349
+ flags = plain_render_flags(format)
350
350
  format = format[:format] if format.is_a?(Hash)
351
351
 
352
352
  # Default to parsed_format if available (preserves input format on round-trip)
@@ -358,7 +358,42 @@ module Pubid
358
358
  # NIST's `to_s` takes a POSITIONAL format and its `render` override
359
359
  # builds no rendering context, so neither the flag nor the context
360
360
  # reaches the shared hook. Apply the annotation fallback here.
361
- annotate_plain_render(rendered, annotated: annotated)
361
+ annotate_plain_render(rendered, **flags)
362
+ end
363
+
364
+ # The keyword flags a positional-format `to_s` hands to
365
+ # `annotate_plain_render`. Ruby fills the positional `format` with a
366
+ # Hash when a caller passes keywords, so they arrive here.
367
+ #
368
+ # `with_publisher: false` cuts the leading `NIST`/`NBS` from the
369
+ # finished string ({#publisher_prefixes}), which covers every place a
370
+ # NIST style prints a publisher — the compound `NBS …` series, the
371
+ # `default_publisher` subclasses and a supplement's base — in one
372
+ # step.
373
+ def plain_render_flags(format)
374
+ return {} unless format.is_a?(Hash)
375
+
376
+ format.slice(:annotated, :with_publisher)
377
+ end
378
+ private :plain_render_flags
379
+
380
+ # The MR style (`NIST.SP.800-53`) is a fixed machine form and ignores
381
+ # `with_publisher:`. The test reads the OUTPUT, not the requested
382
+ # format: several subclasses pick their style themselves (an id parsed
383
+ # from an MR string still renders the short style), so the format a
384
+ # caller asked for does not say which style came back.
385
+ def apply_with_publisher(result, format, with_publisher)
386
+ mr_style = result.is_a?(String) && result.match?(/\A(?:NIST|NBS)\./)
387
+ return result if mr_style
388
+
389
+ super
390
+ end
391
+ private :apply_with_publisher
392
+
393
+ # Every leading publisher token a NIST short style can print: the
394
+ # parsed publisher, and the two `default_publisher` values.
395
+ def publisher_prefixes
396
+ [publisher.to_s, "NIST", "NBS"]
362
397
  end
363
398
 
364
399
  # NIST has its own defined MR format (to_mr_style) used by relaton-nist
@@ -57,7 +57,7 @@ module Pubid
57
57
  # string: with the flag set, `super` returns a string ending in
58
58
  # "</span>" and the anchor no longer matches. Render plain, rewrite,
59
59
  # then annotate.
60
- annotated = format.is_a?(Hash) ? format[:annotated] : nil
60
+ flags = plain_render_flags(format)
61
61
  format = format[:format] if format.is_a?(Hash)
62
62
 
63
63
  result = super(format)
@@ -72,7 +72,7 @@ module Pubid
72
72
  "e#{edition.id || '?'}.#{edition.additional_text}")
73
73
  end
74
74
 
75
- annotate_plain_render(result, annotated: annotated)
75
+ annotate_plain_render(result, **flags)
76
76
  end
77
77
  end
78
78
  end
@@ -41,7 +41,7 @@ module Pubid
41
41
  # Hash. Normalise it away before `super`, so the annotation is applied
42
42
  # once, here, and covers the date-range branch too — that branch
43
43
  # returns a hand-composed string and never reached `super` at all.
44
- annotated = format.is_a?(Hash) ? format[:annotated] : nil
44
+ flags = plain_render_flags(format)
45
45
  format = format[:format] || :short if format.is_a?(Hash)
46
46
 
47
47
  # Handle date range supplements (no base identifier)
@@ -52,7 +52,7 @@ module Pubid
52
52
  super(format)
53
53
  end
54
54
 
55
- annotate_plain_render(result, annotated: annotated)
55
+ annotate_plain_render(result, **flags)
56
56
  end
57
57
  end
58
58
  end
@@ -45,7 +45,7 @@ module Pubid
45
45
  # a Hash — Ruby converts keywords for a method that accepts none.
46
46
  # The same idiom `Identifiers::Base#to_s` uses. This renderer
47
47
  # ignores `format`, so only the flag is read.
48
- annotated = format.is_a?(Hash) ? format[:annotated] : nil
48
+ flags = plain_render_flags(format)
49
49
 
50
50
  result = "#{publisher} #{series}"
51
51
 
@@ -61,7 +61,7 @@ module Pubid
61
61
  result += " #{vol_str}n#{issue_number.number}"
62
62
  end
63
63
 
64
- annotate_plain_render(result, annotated: annotated)
64
+ annotate_plain_render(result, **flags)
65
65
  end
66
66
  end
67
67
  end
@@ -91,7 +91,7 @@ module Pubid
91
91
  # a Hash — Ruby converts keywords for a method that accepts none.
92
92
  # The same idiom `Identifiers::Base#to_s` uses. This renderer
93
93
  # ignores `format`, so only the flag is read.
94
- annotated = format.is_a?(Hash) ? format[:annotated] : nil
94
+ flags = plain_render_flags(format)
95
95
 
96
96
  # Use actual series attribute if it contains subseries (e.g., "CRPL-F-B")
97
97
  # Otherwise use default series_code ("CRPL")
@@ -129,7 +129,7 @@ module Pubid
129
129
  result += append_short_components
130
130
 
131
131
  result += range_notation if range_notation
132
- annotate_plain_render(result, annotated: annotated)
132
+ annotate_plain_render(result, **flags)
133
133
  end
134
134
  end
135
135
  end
@@ -38,7 +38,7 @@ module Pubid
38
38
  # string: with the flag set, `super` returns a string ending in
39
39
  # "</span>" and the anchor no longer matches. Render plain, rewrite,
40
40
  # then annotate.
41
- annotated = format.is_a?(Hash) ? format[:annotated] : nil
41
+ flags = plain_render_flags(format)
42
42
  format = format[:format] if format.is_a?(Hash)
43
43
 
44
44
  # Use the base to_s method first
@@ -50,7 +50,7 @@ module Pubid
50
50
  "#{edition.type}#{edition.id}-#{edition.additional_text}")
51
51
  end
52
52
 
53
- annotate_plain_render(result, annotated: annotated)
53
+ annotate_plain_render(result, **flags)
54
54
  end
55
55
  end
56
56
  end
@@ -40,7 +40,7 @@ module Pubid
40
40
  # `super` is handed a SYMBOL below, so the base's annotation hook
41
41
  # never sees the flag. Capture it here and wrap what comes back —
42
42
  # exactly once, because the base returned plain text.
43
- annotated = format.is_a?(Hash) ? format[:annotated] : nil
43
+ flags = plain_render_flags(format)
44
44
 
45
45
  # Handle both keyword argument (hash) and positional argument (symbol/string)
46
46
  effective_format = if format.is_a?(Hash)
@@ -56,7 +56,7 @@ module Pubid
56
56
  super(effective_format)
57
57
  end
58
58
 
59
- annotate_plain_render(rendered, annotated: annotated)
59
+ annotate_plain_render(rendered, **flags)
60
60
  end
61
61
  end
62
62
  end