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
@@ -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
 
@@ -0,0 +1,36 @@
1
+ # JIS flavor notes
2
+
3
+ ## WES lives in the JIS flavor (relaton-jis#18)
4
+
5
+ - WES (Japan Welding Engineering Society) ids — `WES 7700-1:2019`,
6
+ `WES 7301:2020R` — parse as `Identifiers::WeldingEngineeringStandard`
7
+ (`_type: pubid:jis:welding-engineering-standard`). JSA Webdesk sells WES next
8
+ to JIS and relaton crawls both into one `relaton-data-jis` index, so a
9
+ separate `Pubid::Wes` flavor was rejected.
10
+ - **Grammar**: `data/parg/jis.json` is `identifier = wes_identifier /
11
+ jis_identifier`. The WES branch is `"WES" space number parts …` with the same
12
+ tail as JIS (year, `R`, language, 規格群, supplements, SYMBOL) and binds a
13
+ `wes` capture, which the builder keys on. `JIS WES 7700` and `WES X 7700`
14
+ are rejected: the JIS prefix and a series letter belong only to JIS. The
15
+ source of record is pubid/pubid-grammar; the artifact was rebaked here with
16
+ parsanol **1.3.114** — the PATH `parsanol` shim may be a newer version, so
17
+ bake with the 1.3.114 gem's `exe/parsanol` and check `compiler_version`.
18
+ - **`series` is nil** for WES (JIS keeps its `""`-coerced String), so the hash
19
+ carries no `series` key. `number` is the bare number (`"7700"`), the index
20
+ key.
21
+ - **"WES" is the root, not a publisher**: `publisher_prefix` is nil, `code`
22
+ is `"WES 7700-1"`, so `to_s(with_publisher: false)` still prints `WES …`,
23
+ also as the base of a supplement. The URN is `urn:jis:WES 7700-1:2019`;
24
+ `UrnParser` does not prepend `JIS ` to a code that starts with `WES `.
25
+ - `PREFIXES` includes `"WES"`, so `Pubid.parse("WES …")` routes to JIS.
26
+
27
+ ## Equality includes the class
28
+
29
+ - `Identifier#==` requires `other.instance_of?(self.class)`: `JIS TR X
30
+ 0014:1999` is not `JIS X 0014:1999` (3 such pairs are in the published
31
+ index), and `WES 7700` is never a JIS standard. `#matches?` follows, since it
32
+ compares `exclude(…)` results with `==`.
33
+ - `SupplementIdentifier#==` also compares `symbol` and `reaffirmed?`:
34
+ `…/AMD 3:2025R SYMBOL 65700` is not `…/AMD 3:2025`.
35
+ - The all-parts match is `===` (`Pubid::AllParts`), not `==`, and is
36
+ unchanged.
@@ -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)
@@ -35,72 +37,59 @@ module Pubid
35
37
 
36
38
  def build_single_identifier(data)
37
39
  attrs = {
38
- series: data[:series].to_s,
39
40
  number: data[:number].to_s,
40
41
  parts: extract_part_strings(data[:parts]),
41
42
  year: data[:year]&.to_i,
42
43
  language: data[:language]&.to_s,
43
44
  reaffirmed: (true if data[:reaffirmed]),
44
45
  }
46
+ # A WES id carries no series letter, so its series stays nil.
47
+ wes = Identifiers::WeldingEngineeringStandard
48
+ return wes.new(**attrs) if data[:wes]
45
49
 
46
- # Determine identifier class from type
47
- type = data[:type]&.to_s
48
- klass = case type
49
- when "TR"
50
- Identifiers::TechnicalReport
51
- when "TS"
52
- Identifiers::TechnicalSpecification
53
- else
54
- Identifiers::JapaneseIndustrialStandard
55
- end
50
+ jis_class_for_type(data[:type]&.to_s)
51
+ .new(series: data[:series].to_s, **attrs)
52
+ end
56
53
 
57
- klass.new(**attrs)
54
+ def jis_class_for_type(type)
55
+ case type
56
+ when "TR" then Identifiers::TechnicalReport
57
+ when "TS" then Identifiers::TechnicalSpecification
58
+ else Identifiers::JapaneseIndustrialStandard
59
+ end
58
60
  end
59
61
 
60
62
  def build_amendment(data)
61
- amd_data = data[:amendment]
62
-
63
- # Build base document (without supplement)
64
- base_data = data.except(:amendment)
65
- base = build_single_identifier(base_data)
63
+ # The bindings carry the supplement's fields at the top level; the
64
+ # base document shares them.
65
+ base = build_single_identifier(data)
66
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]
76
+ base = build_single_identifier(data)
78
77
 
79
- # Build base document (without supplement)
80
- base_data = data.except(:explanation)
81
- base = build_single_identifier(base_data)
82
-
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
 
@@ -43,6 +43,8 @@ module Pubid
43
43
  "pubid:jis:amendment" => "Pubid::Jis::Identifiers::Amendment",
44
44
  "pubid:jis:corrigendum" => "Pubid::Jis::Identifiers::Corrigendum",
45
45
  "pubid:jis:explanation" => "Pubid::Jis::Identifiers::Explanation",
46
+ "pubid:jis:welding-engineering-standard" =>
47
+ "Pubid::Jis::Identifiers::WeldingEngineeringStandard",
46
48
  }.freeze
47
49
 
48
50
  key_value do
@@ -63,6 +65,12 @@ module Pubid
63
65
  # would otherwise fail serialization type validation.
64
66
  PUBLISHER = "JIS"
65
67
 
68
+ # The token the human render prints before the designation, and drops
69
+ # for `to_s(with_publisher: false)`; nil when the designation has none.
70
+ def publisher_prefix
71
+ PUBLISHER
72
+ end
73
+
66
74
  def reaffirmed?
67
75
  reaffirmed == true
68
76
  end
@@ -88,8 +96,10 @@ module Pubid
88
96
  result
89
97
  end
90
98
 
99
+ # The class is part of the identity: "JIS TR X 0014:1999" is not
100
+ # "JIS X 0014:1999", and "WES 7700" is not a JIS standard.
91
101
  def ==(other)
92
- return false unless other.is_a?(Identifier)
102
+ return false unless other.instance_of?(self.class)
93
103
 
94
104
  series == other.series &&
95
105
  number == other.number &&
@@ -174,7 +184,7 @@ module Pubid
174
184
  raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
175
185
  end
176
186
 
177
- parsed = Pubid::Parg::Backend.parse(:jis, identifier)
187
+ parsed = Pubid::Parg::Model.bound(:jis, identifier)
178
188
  Builder.build(parsed)
179
189
  end
180
190
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Jis
5
+ module Identifiers
6
+ # Japan Welding Engineering Society standard. JSA sells WES next to JIS,
7
+ # so it parses through the JIS flavor, but the id carries no series
8
+ # letter and no JIS prefix.
9
+ # Examples: WES 7700-1:2019, WES 7301:2020R
10
+ class WeldingEngineeringStandard < SingleIdentifier
11
+ PUBLISHER = "WES"
12
+
13
+ def type_prefix
14
+ nil
15
+ end
16
+
17
+ # "WES" is the root of the designation, not a publisher token, so
18
+ # `to_s(with_publisher: false)` keeps it.
19
+ def publisher_prefix
20
+ nil
21
+ end
22
+
23
+ def code
24
+ result = "#{PUBLISHER} #{number}"
25
+ result += parts.map { |p| "-#{p}" }.join if parts&.any?
26
+ result
27
+ end
28
+
29
+ def mr_publisher
30
+ PUBLISHER.downcase
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -13,6 +13,8 @@ module Pubid
13
13
  autoload :TechnicalReport, "#{__dir__}/identifiers/technical_report"
14
14
  autoload :TechnicalSpecification,
15
15
  "#{__dir__}/identifiers/technical_specification"
16
+ autoload :WeldingEngineeringStandard,
17
+ "#{__dir__}/identifiers/welding_engineering_standard"
16
18
  end
17
19
  end
18
20
  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
@@ -44,7 +44,7 @@ module Pubid
44
44
  def render_single(id)
45
45
  with_pub = with_publisher?(id)
46
46
  parts = []
47
- parts << PUBLISHER if with_pub
47
+ parts << id.publisher_prefix if with_pub && id.publisher_prefix
48
48
  parts << id.type_prefix if id.class.method_defined?(:type_prefix) && id.type_prefix
49
49
  result = parts.join(" ")
50
50
  result += " " if result.length.positive?
@@ -38,12 +38,12 @@ module Pubid
38
38
  end
39
39
 
40
40
  def ==(other)
41
- return false unless other.is_a?(SupplementIdentifier)
42
- return false unless other.class == self.class
41
+ return false unless other.instance_of?(self.class)
43
42
 
44
- base == other.base &&
45
- number == other.number &&
46
- year == other.year
43
+ # A supplement carries its own reaffirmation and SYMBOL clause.
44
+ %i[base number year reaffirmed? symbol].all? do |field|
45
+ public_send(field) == other.public_send(field)
46
+ end
47
47
  end
48
48
  end
49
49
  end
@@ -9,12 +9,13 @@ module Pubid
9
9
  #
10
10
  # Examples:
11
11
  # - urn:jis:X 0201:1997 → JIS X 0201:1997
12
+ # - urn:jis:WES 7700-1:2019 → WES 7700-1:2019 (the code carries its root)
12
13
  class UrnParser < Pubid::UrnParser::Base
13
14
  def parse_urn(urn)
14
15
  body = strip_namespace(urn)
15
16
  parts = split_parts(body)
16
17
  code, year = parts
17
- text = "JIS #{code}"
18
+ text = code.to_s.start_with?("WES ") ? code.dup : "JIS #{code}"
18
19
  text += ":#{year}" if year
19
20
  flavor_parse(text)
20
21
  end
data/lib/pubid/jis.rb CHANGED
@@ -5,9 +5,10 @@ module Pubid
5
5
  module Jis
6
6
  extend Pubid::PrefixesSupport
7
7
 
8
- # Sole JIS publisher token (see the parser's `jis_prefix` rule). Document
8
+ # The JIS publisher token, and WES, the root of a Japan Welding
9
+ # Engineering Society id (the grammar's `wes_identifier` rule). Document
9
10
  # types (TR/TS) follow the JIS prefix and are excluded.
10
- PREFIXES = ["JIS"].freeze
11
+ PREFIXES = ["JIS", "WES"].freeze
11
12
 
12
13
  autoload :Builder, "#{__dir__}/jis/builder"
13
14
  autoload :Identifier, "#{__dir__}/jis/identifier"
@@ -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