pubid 2.0.0.pre.alpha.11 → 2.0.0.pre.alpha.13

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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +43 -1
  3. data/data/ieee/update_codes.yaml +17 -4
  4. data/data/nist/update_codes.yaml +7 -3
  5. data/lib/pubid/adobe/builder.rb +2 -0
  6. data/lib/pubid/all_parts.rb +201 -0
  7. data/lib/pubid/all_parts_identifier.rb +19 -0
  8. data/lib/pubid/amca/CLAUDE.md +47 -0
  9. data/lib/pubid/amca/builder.rb +3 -5
  10. data/lib/pubid/amca/identifiers/base.rb +10 -0
  11. data/lib/pubid/amca/identifiers/publication.rb +13 -0
  12. data/lib/pubid/amca/parser.rb +2 -1
  13. data/lib/pubid/amca/renderer.rb +22 -33
  14. data/lib/pubid/amca/urn_generator.rb +21 -2
  15. data/lib/pubid/amca/urn_parser.rb +36 -10
  16. data/lib/pubid/ansi/builder.rb +6 -0
  17. data/lib/pubid/api/CLAUDE.md +23 -0
  18. data/lib/pubid/api/builder.rb +2 -0
  19. data/lib/pubid/ashrae/CLAUDE.md +13 -0
  20. data/lib/pubid/ashrae/builder.rb +58 -14
  21. data/lib/pubid/ashrae/identifiers/errata.rb +14 -2
  22. data/lib/pubid/ashrae/identifiers/interpretation.rb +2 -10
  23. data/lib/pubid/ashrae/parser.rb +62 -28
  24. data/lib/pubid/ashrae/renderer.rb +32 -1
  25. data/lib/pubid/ashrae/urn_generator.rb +32 -9
  26. data/lib/pubid/asme/CLAUDE.md +25 -0
  27. data/lib/pubid/asme/builder.rb +16 -9
  28. data/lib/pubid/asme/components/code.rb +2 -0
  29. data/lib/pubid/asme/identifiers/standard.rb +6 -1
  30. data/lib/pubid/asme/parser.rb +41 -14
  31. data/lib/pubid/astm/CLAUDE.md +9 -0
  32. data/lib/pubid/astm/builder.rb +2 -0
  33. data/lib/pubid/astm/components/code.rb +2 -0
  34. data/lib/pubid/astm/parser.rb +4 -1
  35. data/lib/pubid/bipm/CLAUDE.md +11 -0
  36. data/lib/pubid/bipm/builder.rb +2 -0
  37. data/lib/pubid/bsi/CLAUDE.md +93 -0
  38. data/lib/pubid/bsi/builder.rb +13 -11
  39. data/lib/pubid/bsi/components/date.rb +10 -4
  40. data/lib/pubid/bsi/identifiers/addendum_document.rb +2 -0
  41. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +6 -54
  42. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +5 -22
  43. data/lib/pubid/bsi/identifiers/amendment.rb +36 -12
  44. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +1 -1
  45. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +2 -0
  46. data/lib/pubid/bsi/identifiers/consolidated_identifier.rb +23 -26
  47. data/lib/pubid/bsi/identifiers/corrigendum.rb +29 -12
  48. data/lib/pubid/bsi/identifiers/expert_commentary.rb +6 -7
  49. data/lib/pubid/bsi/identifiers/handbook.rb +1 -1
  50. data/lib/pubid/bsi/identifiers/national_annex.rb +18 -20
  51. data/lib/pubid/bsi/identifiers/practice_guide.rb +1 -1
  52. data/lib/pubid/bsi/identifiers/root_identity.rb +31 -0
  53. data/lib/pubid/bsi/identifiers/set.rb +2 -0
  54. data/lib/pubid/bsi/identifiers/supplement_document.rb +2 -0
  55. data/lib/pubid/bsi/identifiers.rb +1 -0
  56. data/lib/pubid/bsi/parser.rb +8 -8
  57. data/lib/pubid/bsi/renderer.rb +20 -20
  58. data/lib/pubid/bsi/single_identifier.rb +11 -2
  59. data/lib/pubid/bsi/urn_generator.rb +28 -18
  60. data/lib/pubid/builder/base.rb +27 -0
  61. data/lib/pubid/calconnect/builder.rb +2 -0
  62. data/lib/pubid/calconnect/identifier.rb +4 -0
  63. data/lib/pubid/ccsds/builder.rb +2 -0
  64. data/lib/pubid/ccsds/identifier.rb +12 -0
  65. data/lib/pubid/ccsds/single_identifier.rb +14 -10
  66. data/lib/pubid/cen_cenelec/CLAUDE.md +59 -0
  67. data/lib/pubid/cen_cenelec/builder.rb +6 -1
  68. data/lib/pubid/cen_cenelec/identifier.rb +10 -27
  69. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +3 -10
  70. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +3 -10
  71. data/lib/pubid/cen_cenelec/parser.rb +11 -3
  72. data/lib/pubid/cie/CLAUDE.md +58 -0
  73. data/lib/pubid/cie/builder.rb +2 -0
  74. data/lib/pubid/cie/components/language.rb +2 -0
  75. data/lib/pubid/components/adoption.rb +2 -0
  76. data/lib/pubid/components/code.rb +2 -0
  77. data/lib/pubid/components/date.rb +8 -6
  78. data/lib/pubid/components/edition.rb +2 -0
  79. data/lib/pubid/components/iteration.rb +2 -0
  80. data/lib/pubid/components/language.rb +2 -0
  81. data/lib/pubid/components/locality.rb +2 -0
  82. data/lib/pubid/components/publisher.rb +2 -0
  83. data/lib/pubid/components/relationship.rb +2 -0
  84. data/lib/pubid/components/stage.rb +2 -0
  85. data/lib/pubid/components/supplement.rb +2 -0
  86. data/lib/pubid/components/type.rb +2 -0
  87. data/lib/pubid/components/typed_stage.rb +8 -0
  88. data/lib/pubid/csa/CLAUDE.md +41 -0
  89. data/lib/pubid/csa/builder.rb +5 -3
  90. data/lib/pubid/csa/identifier.rb +13 -1
  91. data/lib/pubid/csa/identifiers/bundled.rb +2 -2
  92. data/lib/pubid/csa/identifiers/cec.rb +1 -1
  93. data/lib/pubid/csa/renderer.rb +12 -12
  94. data/lib/pubid/csa/single_identifier.rb +18 -1
  95. data/lib/pubid/csa/urn_generator.rb +1 -1
  96. data/lib/pubid/doi/builder.rb +2 -0
  97. data/lib/pubid/easc/builder.rb +2 -0
  98. data/lib/pubid/ecma/CLAUDE.md +28 -0
  99. data/lib/pubid/ecma/builder.rb +2 -0
  100. data/lib/pubid/ecma/identifier.rb +7 -0
  101. data/lib/pubid/etsi/CLAUDE.md +34 -0
  102. data/lib/pubid/etsi/builder.rb +2 -0
  103. data/lib/pubid/etsi/components/code.rb +6 -0
  104. data/lib/pubid/etsi/components/version.rb +2 -0
  105. data/lib/pubid/etsi/identifiers/etsi_standard.rb +7 -0
  106. data/lib/pubid/evs/CLAUDE.md +58 -0
  107. data/lib/pubid/evs/builder.rb +3 -1
  108. data/lib/pubid/evs/identifier.rb +6 -6
  109. data/lib/pubid/evs/identifiers/national_adoption.rb +7 -2
  110. data/lib/pubid/evs/renderer.rb +1 -1
  111. data/lib/pubid/evs/urn_generator.rb +1 -1
  112. data/lib/pubid/evs/urn_parser.rb +1 -1
  113. data/lib/pubid/gb/CLAUDE.md +140 -0
  114. data/lib/pubid/gb/builder.rb +21 -11
  115. data/lib/pubid/gb/identifier.rb +28 -7
  116. data/lib/pubid/gb/identifiers/all_parts.rb +17 -0
  117. data/lib/pubid/gb/identifiers.rb +1 -0
  118. data/lib/pubid/gb/parser.rb +4 -0
  119. data/lib/pubid/gb/renderer.rb +4 -4
  120. data/lib/pubid/gb.rb +1 -0
  121. data/lib/pubid/gost/CLAUDE.md +64 -0
  122. data/lib/pubid/gost/builder.rb +3 -1
  123. data/lib/pubid/gost/identifier.rb +5 -0
  124. data/lib/pubid/iala/CLAUDE.md +82 -0
  125. data/lib/pubid/iala/builder.rb +2 -0
  126. data/lib/pubid/iana/CLAUDE.md +7 -0
  127. data/lib/pubid/iana/builder.rb +2 -0
  128. data/lib/pubid/identifier.rb +172 -20
  129. data/lib/pubid/idf/builder.rb +6 -1
  130. data/lib/pubid/idf/identifier.rb +12 -0
  131. data/lib/pubid/idf/identifiers/all_parts.rb +17 -0
  132. data/lib/pubid/idf/identifiers/amendment.rb +1 -1
  133. data/lib/pubid/idf/identifiers/corrigendum.rb +1 -1
  134. data/lib/pubid/idf/identifiers/international_standard.rb +1 -1
  135. data/lib/pubid/idf/identifiers/reviewed_method.rb +1 -1
  136. data/lib/pubid/idf/identifiers.rb +1 -0
  137. data/lib/pubid/idf/single_identifier.rb +1 -1
  138. data/lib/pubid/iec/CLAUDE.md +31 -0
  139. data/lib/pubid/iec/builder.rb +7 -1
  140. data/lib/pubid/iec/components/consolidated_amendment.rb +4 -0
  141. data/lib/pubid/iec/components/sheet.rb +2 -0
  142. data/lib/pubid/iec/components/trf_info.rb +2 -0
  143. data/lib/pubid/iec/components/vap_suffix.rb +2 -0
  144. data/lib/pubid/iec/identifier.rb +7 -2
  145. data/lib/pubid/iec/identifiers/all_parts.rb +19 -0
  146. data/lib/pubid/iec/identifiers.rb +1 -0
  147. data/lib/pubid/iec/renderer.rb +0 -1
  148. data/lib/pubid/iec/urn_generator.rb +9 -1
  149. data/lib/pubid/iec/urn_parser.rb +3 -2
  150. data/lib/pubid/ieee/CLAUDE.md +97 -0
  151. data/lib/pubid/ieee/builder.rb +176 -7
  152. data/lib/pubid/ieee/components/code.rb +2 -0
  153. data/lib/pubid/ieee/components/draft.rb +35 -2
  154. data/lib/pubid/ieee/components/typed_stage.rb +2 -0
  155. data/lib/pubid/ieee/identifiers/base.rb +20 -0
  156. data/lib/pubid/ieee/identifiers/iec_ieee_copublished.rb +9 -0
  157. data/lib/pubid/ieee/identifiers/joint_development.rb +17 -10
  158. data/lib/pubid/ieee/identifiers/project_draft_identifier.rb +8 -1
  159. data/lib/pubid/ieee/identifiers/si_standard.rb +5 -2
  160. data/lib/pubid/ieee/ire/parser.rb +19 -1
  161. data/lib/pubid/ieee/parser.rb +337 -49
  162. data/lib/pubid/ieee/project_renderer.rb +46 -0
  163. data/lib/pubid/ieee/renderer.rb +46 -14
  164. data/lib/pubid/ieee/urn_generator.rb +31 -0
  165. data/lib/pubid/ieee.rb +2 -0
  166. data/lib/pubid/ietf/CLAUDE.md +7 -0
  167. data/lib/pubid/ietf/builder.rb +2 -0
  168. data/lib/pubid/iho/builder.rb +2 -0
  169. data/lib/pubid/isbn/builder.rb +2 -0
  170. data/lib/pubid/iso/CLAUDE.md +47 -0
  171. data/lib/pubid/iso/builder.rb +29 -16
  172. data/lib/pubid/iso/components/publisher.rb +2 -0
  173. data/lib/pubid/iso/components.rb +0 -1
  174. data/lib/pubid/iso/identifier.rb +17 -18
  175. data/lib/pubid/iso/identifiers/all_parts.rb +19 -0
  176. data/lib/pubid/iso/identifiers/directives.rb +7 -4
  177. data/lib/pubid/iso/identifiers/directives_supplement.rb +4 -2
  178. data/lib/pubid/iso/identifiers/tc_document.rb +31 -46
  179. data/lib/pubid/iso/identifiers.rb +1 -0
  180. data/lib/pubid/iso/normalizer.rb +1 -1
  181. data/lib/pubid/iso/rendering_style.rb +0 -1
  182. data/lib/pubid/iso/urn_generator.rb +10 -10
  183. data/lib/pubid/iso.rb +5 -4
  184. data/lib/pubid/itu/CLAUDE.md +69 -0
  185. data/lib/pubid/itu/builder.rb +12 -0
  186. data/lib/pubid/itu/components/code.rb +2 -0
  187. data/lib/pubid/itu/components/designation.rb +2 -0
  188. data/lib/pubid/itu/components/sector.rb +2 -0
  189. data/lib/pubid/itu/components/series.rb +2 -0
  190. data/lib/pubid/itu/identifiers/base.rb +10 -0
  191. data/lib/pubid/itu/identifiers/contribution.rb +31 -0
  192. data/lib/pubid/itu/identifiers/supplement.rb +15 -0
  193. data/lib/pubid/itu/identifiers.rb +1 -0
  194. data/lib/pubid/itu/parser.rb +19 -2
  195. data/lib/pubid/jcgm/CLAUDE.md +7 -0
  196. data/lib/pubid/jcgm/builder.rb +2 -0
  197. data/lib/pubid/jcgm/components/publisher.rb +2 -0
  198. data/lib/pubid/jis/builder.rb +5 -1
  199. data/lib/pubid/jis/identifier.rb +5 -17
  200. data/lib/pubid/jis/identifiers/all_parts.rb +19 -0
  201. data/lib/pubid/jis/identifiers.rb +1 -0
  202. data/lib/pubid/jis/renderer.rb +0 -2
  203. data/lib/pubid/jis/urn_generator.rb +0 -1
  204. data/lib/pubid/nist/CLAUDE.md +56 -0
  205. data/lib/pubid/nist/builder.rb +14 -12
  206. data/lib/pubid/nist/components/edition.rb +2 -0
  207. data/lib/pubid/nist/components/issue_number.rb +2 -0
  208. data/lib/pubid/nist/components/part.rb +2 -0
  209. data/lib/pubid/nist/components/stage.rb +2 -0
  210. data/lib/pubid/nist/components/supplement.rb +2 -0
  211. data/lib/pubid/nist/components/translation.rb +2 -0
  212. data/lib/pubid/nist/components/update.rb +2 -0
  213. data/lib/pubid/nist/components/version.rb +2 -0
  214. data/lib/pubid/nist/components/volume.rb +2 -0
  215. data/lib/pubid/nist/identifiers/base.rb +34 -6
  216. data/lib/pubid/nist/identifiers/commercial_standard_emergency.rb +1 -1
  217. data/lib/pubid/nist/identifiers/crpl_report.rb +5 -6
  218. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +2 -2
  219. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +2 -2
  220. data/lib/pubid/nist/parser.rb +15 -2
  221. data/lib/pubid/nist/preprocessor.rb +53 -2
  222. data/lib/pubid/nist/series/ir.rb +3 -7
  223. data/lib/pubid/oasis/CLAUDE.md +19 -0
  224. data/lib/pubid/oasis/builder.rb +2 -0
  225. data/lib/pubid/oasis/identifier.rb +19 -0
  226. data/lib/pubid/ogc/CLAUDE.md +34 -0
  227. data/lib/pubid/ogc/builder.rb +2 -0
  228. data/lib/pubid/ogc/identifier.rb +11 -0
  229. data/lib/pubid/oiml/CLAUDE.md +189 -0
  230. data/lib/pubid/oiml/builder.rb +22 -2
  231. data/lib/pubid/oiml/components/code.rb +6 -0
  232. data/lib/pubid/oiml/identifier.rb +13 -0
  233. data/lib/pubid/oiml/identifiers/annex.rb +4 -0
  234. data/lib/pubid/oiml/identifiers/bulletin.rb +30 -36
  235. data/lib/pubid/oiml/identifiers/certification_system.rb +34 -0
  236. data/lib/pubid/oiml/identifiers/code_number.rb +10 -2
  237. data/lib/pubid/oiml/identifiers/dual_published.rb +174 -0
  238. data/lib/pubid/oiml/identifiers.rb +2 -0
  239. data/lib/pubid/oiml/parser.rb +41 -5
  240. data/lib/pubid/oiml/renderer.rb +26 -4
  241. data/lib/pubid/oiml/single_identifier.rb +5 -1
  242. data/lib/pubid/oiml/supplement_identifier.rb +7 -0
  243. data/lib/pubid/oiml/urn_generator.rb +32 -4
  244. data/lib/pubid/oiml.rb +5 -0
  245. data/lib/pubid/omg/CLAUDE.md +15 -0
  246. data/lib/pubid/omg/builder.rb +2 -0
  247. data/lib/pubid/parser/grammar.rb +23 -0
  248. data/lib/pubid/plateau/builder.rb +2 -0
  249. data/lib/pubid/plateau/identifiers/base.rb +4 -0
  250. data/lib/pubid/plateau/supplement_identifier.rb +14 -2
  251. data/lib/pubid/plateau/urn_generator.rb +7 -1
  252. data/lib/pubid/renderers/directives_renderer.rb +3 -1
  253. data/lib/pubid/renderers/human_readable.rb +0 -1
  254. data/lib/pubid/sae/builder.rb +3 -1
  255. data/lib/pubid/sae/components/date.rb +2 -0
  256. data/lib/pubid/sae/components/type.rb +2 -0
  257. data/lib/pubid/sae/identifiers/base.rb +4 -1
  258. data/lib/pubid/sae/urn_generator.rb +1 -1
  259. data/lib/pubid/subset_match.rb +197 -0
  260. data/lib/pubid/tgpp/CLAUDE.md +43 -0
  261. data/lib/pubid/tgpp/builder.rb +2 -0
  262. data/lib/pubid/tgpp/identifier.rb +14 -0
  263. data/lib/pubid/type_resolver.rb +14 -2
  264. data/lib/pubid/un/builder.rb +2 -0
  265. data/lib/pubid/version.rb +1 -1
  266. data/lib/pubid/w3c/CLAUDE.md +7 -0
  267. data/lib/pubid/w3c/builder.rb +2 -0
  268. data/lib/pubid/xsf/CLAUDE.md +11 -0
  269. data/lib/pubid/xsf/builder.rb +2 -0
  270. data/lib/pubid.rb +16 -3
  271. data/lib/tasks/docs.rake +13 -11
  272. metadata +46 -4
  273. data/lib/pubid/ccsds/identifiers/base_BASE_88929.rb +0 -70
  274. data/lib/pubid/iso/components/code.rb +0 -24
@@ -7,6 +7,11 @@ module Pubid
7
7
  # Pubid::Jis::Identifiers descend from this class, so a parsed JIS id is an
8
8
  # instance of Pubid::Jis::Identifier.
9
9
  class Identifier < ::Pubid::Identifier
10
+ # JIS prints its own all-parts suffix, "(規格群)".
11
+ def self.all_parts_class
12
+ Identifiers::AllParts
13
+ end
14
+
10
15
  # JIS keeps its number flat at the top level (string, to preserve leading
11
16
  # zeros like "0205"), with the division letter in `series` and any
12
17
  # multi-level part numbers in `parts`. Supplements override `number` with
@@ -16,9 +21,6 @@ module Pubid
16
21
  attribute :parts, :string, collection: true # Optional multi-level parts
17
22
  attribute :year, :integer
18
23
  attribute :language, :string # "E" or "J"
19
- # Boolean flags carry no default, so they stay nil (and are omitted from
20
- # the serialized hash) unless actually set true.
21
- attribute :all_parts, :boolean
22
24
  # Reaffirmation (再確認): a trailing "R" on the year marks an edition
23
25
  # that was reaffirmed without revision (e.g. ":2019R").
24
26
  attribute :reaffirmed, :boolean
@@ -50,7 +52,6 @@ module Pubid
50
52
  map "parts", to: :parts
51
53
  map "year", to: :year
52
54
  map "language", to: :language
53
- map "all_parts", to: :all_parts
54
55
  map "reaffirmed", to: :reaffirmed
55
56
  # render_empty keeps a bare "SYMBOL" (empty-string value) in the hash so
56
57
  # it round-trips distinctly from "no symbol" (nil).
@@ -62,10 +63,6 @@ module Pubid
62
63
  # would otherwise fail serialization type validation.
63
64
  PUBLISHER = "JIS"
64
65
 
65
- def all_parts?
66
- all_parts == true
67
- end
68
-
69
66
  def reaffirmed?
70
67
  reaffirmed == true
71
68
  end
@@ -91,23 +88,14 @@ module Pubid
91
88
  result
92
89
  end
93
90
 
94
- # Comparison with all_parts logic
95
- # When either identifier has all_parts=true, compare only series and number
96
91
  def ==(other)
97
92
  return false unless other.is_a?(Identifier)
98
93
 
99
- if all_parts? || other.all_parts?
100
- # Compare only series and number, ignore year, parts, all_parts
101
- return series == other.series && number == other.number
102
- end
103
-
104
- # Normal full comparison
105
94
  series == other.series &&
106
95
  number == other.number &&
107
96
  (parts || []) == (other.parts || []) &&
108
97
  year == other.year &&
109
98
  language == other.language &&
110
- all_parts? == other.all_parts? &&
111
99
  reaffirmed? == other.reaffirmed? &&
112
100
  symbol == other.symbol
113
101
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Jis
5
+ module Identifiers
6
+ # Every part of one JIS document: "JIS C 0617(規格群)".
7
+ class AllParts < ::Pubid::Jis::Identifier
8
+ include ::Pubid::AllParts
9
+
10
+ SUFFIX = "(規格群)"
11
+
12
+ # The URN of the document plus the "all" slot.
13
+ def to_urn
14
+ "#{identity.to_urn}:all"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -3,6 +3,7 @@
3
3
  module Pubid
4
4
  module Jis
5
5
  module Identifiers
6
+ autoload :AllParts, "#{__dir__}/identifiers/all_parts"
6
7
  autoload :Amendment, "#{__dir__}/identifiers/amendment"
7
8
  autoload :Corrigendum, "#{__dir__}/identifiers/corrigendum"
8
9
  autoload :Explanation, "#{__dir__}/identifiers/explanation"
@@ -38,7 +38,6 @@ module Pubid
38
38
  result = "#{PUBLISHER} #{id.code}"
39
39
  result += ":#{id.year_with_reaffirmation}" if id.year
40
40
  result += "(#{id.language})" if id.language
41
- result += "(規格群)" if id.all_parts?
42
41
  result + id.symbol_suffix
43
42
  end
44
43
 
@@ -52,7 +51,6 @@ module Pubid
52
51
  result += id.code.to_s
53
52
  result += ":#{id.year_with_reaffirmation}" if id.year
54
53
  result += "(#{id.language})" if id.language
55
- result += "(規格群)" if id.all_parts?
56
54
  result += id.symbol_suffix
57
55
  result
58
56
  end
@@ -12,7 +12,6 @@ module Pubid
12
12
 
13
13
  parts << identifier.language.to_s.downcase if identifier.language
14
14
 
15
- parts << "all" if identifier.all_parts?
16
15
 
17
16
  if identifier.is_a?(SupplementIdentifier) && identifier.supplement_notation
18
17
  parts << identifier.supplement_notation.to_s.downcase
@@ -0,0 +1,56 @@
1
+ # NIST flavor notes
2
+
3
+ NIST rendering formats and annotated output.
4
+
5
+ Read this before you change `lib/pubid/nist/` or `spec/pubid/nist/`. The root
6
+ `CLAUDE.md` keeps the cross-flavor contract that every flavor obeys.
7
+
8
+ - **Six NIST types rendered plain under `to_s(annotated: true)`, and two
9
+ more carried a latent corruption.** NIST's `to_s` takes a **positional**
10
+ `format`, which Ruby fills with a Hash when a caller passes `annotated:`;
11
+ `Identifiers::Base#to_s` already handles that and annotates. The six that
12
+ did not — `CommercialStandardsMonthly`, `CrplReport`, `InteragencyReport`,
13
+ `MiscellaneousPublication`, `Monograph`, `Report` — either compose their
14
+ own string or hand `super` a **Symbol**, which drops the flag. Each now
15
+ pulls `annotated` out of the Hash itself and wraps its result.
16
+
17
+ **`Circular` and `Handbook` are the interesting half.** Both did
18
+ `result = super` — which annotates — and then rewrote the edition with a
19
+ **`$`-anchored** regex. Once the string ends in `</span>` the anchor
20
+ cannot match, so the rewrite silently stops applying. Both now strip the
21
+ flag before `super`, rewrite, then annotate.
22
+
23
+ **An output assertion cannot catch that**, and this is the lesson: for
24
+ both fixture references (`NBS CIRC 11e2-1915`, `NBS HB 44e2-1955`) the
25
+ annotator matches only the leading publisher, so the tail is bare and the
26
+ wrong order still produces the right string. The spec asserts the
27
+ **ordering** instead — it wraps `annotate_plain_render` and checks the
28
+ string handed to it is already rewritten and carries no span — and that
29
+ assertion was verified to fail when the order is reverted.
30
+
31
+ `CircularSupplement` and `SupplementIdentifier` were also normalised:
32
+ both had a date-range branch that returned a hand-composed string without
33
+ ever reaching `super`. `SupplementIdentifier` has four exits, so its
34
+ composition moved into a private `render_plain`; **`super` is not
35
+ reachable from a private method**, so `to_s` hands it in as a block —
36
+ `render_plain(format) { super(format) }`.
37
+
38
+ - **`all_parts_edition_keys` needed `update`/`update_component`, not
39
+ `edition_year`.** `Identifier.all_parts_edition_keys` defaults to
40
+ `%i[date year edition version]`; NIST's primary edition carrier
41
+ (`edition`, a `Components::Edition`) is already covered, but the Letter
42
+ Circular / Circular "rJun1992"-style revision (`Builder` around the
43
+ "Convert revision with month+year to update component" comment) parses
44
+ into a **separate** attribute, `update`/`update_component`
45
+ (`Components::Update`: number+year+month), which the default list
46
+ missed entirely — `"NBS LC 800 rJun1992"` and `"NBS LC 800 rJul1995"`
47
+ failed to collapse under `#to_all_parts`/`#===`. Fixed with
48
+ `Pubid::Nist::Identifier.all_parts_edition_keys` (`super + %i[update
49
+ update_component]`). **`edition_year` and `revision_year`/
50
+ `revision_month` were investigated and are NOT added**: `Builder` only
51
+ ever sets `edition_year` alongside the real `edition` component (never
52
+ as its sole carrier, e.g. the TechnicalNote "date IS edition" branch),
53
+ and `revision_year`/`revision_month` are transient — converted into
54
+ `update`/`update_component` and cleared to `nil` before the object is
55
+ returned. Neither carries live information `edition`/`update` doesn't
56
+ already cover. Locked by `spec/pubid/all_parts_edition_keys_audit_spec.rb`.
@@ -253,7 +253,7 @@ module Pubid
253
253
  elsif decimal_num
254
254
  decimal_base = decimal_num[:decimal_base].to_s
255
255
  decimal_suffix = decimal_num[:decimal_suffix].to_s
256
- identifier.number = Components::Code.new(value: "#{first_num.value}-#{decimal_base}.#{decimal_suffix}")
256
+ identifier.number = "#{first_num.value}-#{decimal_base}.#{decimal_suffix}"
257
257
  # NEW: Handle letter number pattern (e.g., 1-1A, 1-3B for NCSTAR identifiers)
258
258
  # letter_num is {:letter_base => "1", :letter_suffix => "A"}
259
259
  # Also handles IR series "R" suffix: "79-1786R" -> "79-1786r1"
@@ -268,10 +268,10 @@ module Pubid
268
268
  # Series-specific handler took ownership (e.g., IR "R" → r1)
269
269
  elsif identifier.part
270
270
  # SpecialPublication pattern: letter_suffix is separate Part component
271
- identifier.number = Components::Code.new(value: "#{first_num.value}-#{letter_base}")
271
+ identifier.number = "#{first_num.value}-#{letter_base}"
272
272
  else
273
273
  # NCSTAR pattern: letter_suffix is part of the number
274
- identifier.number = Components::Code.new(value: "#{first_num.value}-#{letter_base}#{letter_suffix}")
274
+ identifier.number = "#{first_num.value}-#{letter_base}#{letter_suffix}"
275
275
  end
276
276
  elsif second_num
277
277
  # Check for special patterns first
@@ -285,7 +285,7 @@ module Pubid
285
285
  # Create Edition component
286
286
  edition_obj = Components::Edition.new(type: "r", id: edition_id)
287
287
 
288
- identifier.number = Components::Code.new(value: "#{first_num.value}-#{number_part}")
288
+ identifier.number = "#{first_num.value}-#{number_part}"
289
289
  identifier.edition = edition_obj
290
290
  # CS Emergency pattern: e104-43 -> number=104, edition_year=1943
291
291
  # Logic: e104-43 means "emergency 104 from 1943" (43 = 1943)
@@ -300,7 +300,7 @@ module Pubid
300
300
  # Create Edition component
301
301
  edition_obj = Components::Edition.new(type: "e", id: edition_year)
302
302
 
303
- identifier.number = Components::Code.new(value: number_part)
303
+ identifier.number = number_part
304
304
  identifier.edition = edition_obj
305
305
  elsif first_num.value.to_s.match?(/^(\d+)e(\d+)$/) &&
306
306
  second_num.value.to_s.match?(/^\d{2,4}$/)
@@ -314,7 +314,7 @@ module Pubid
314
314
  # Expand 2-digit year to 4-digit (50 -> 1950)
315
315
  year_part = "19#{year_part}" if year_part.length == 2
316
316
 
317
- identifier.number = Components::Code.new(value: number_part)
317
+ identifier.number = number_part
318
318
 
319
319
  # For edition+year patterns, handling depends on identifier type:
320
320
  # - CIRC: edition number + year as additional_text, rendered with dot ("11e2-1915" -> "11e2.1915")
@@ -329,7 +329,7 @@ module Pubid
329
329
  number_part = first_num.value.to_s.match(/^(\d+)supp?$/)[1]
330
330
  year_part = second_num.value.to_s
331
331
 
332
- identifier.number = Components::Code.new(value: number_part)
332
+ identifier.number = number_part
333
333
  supp[:value] = year_part
334
334
  supp[:present] = true
335
335
  elsif second_num.value.to_s.match?(/^(\d+)supp?$/)
@@ -337,14 +337,14 @@ module Pubid
337
337
  # second number. Strip it and isolate as supplement="" (single-p).
338
338
  second_part = second_num.value.to_s.match(/^(\d+)supp?$/)[1]
339
339
  compound = "#{first_num.value}-#{second_part}"
340
- identifier.number = Components::Code.new(value: compound)
340
+ identifier.number = compound
341
341
  supp[:value] = ""
342
342
  supp[:present] = true
343
343
  elsif identifier.is_a?(Identifiers::TechnicalNote) &&
344
344
  second_num.value.to_s.match?(/^(19|20)\d{2}$/)
345
345
  # SPECIAL CASE FOR TN: second_num is edition year
346
346
  # Following "date IS edition" rule: -1993 becomes Edition(type: "e", id: "1993")
347
- identifier.number = first_num
347
+ identifier.number = first_num.value.to_s
348
348
  edition_obj = Components::Edition.new(type: "e",
349
349
  id: second_num.value.to_s)
350
350
  identifier.edition = edition_obj
@@ -354,16 +354,16 @@ module Pubid
354
354
  # not folded into the compound number.
355
355
  identifier.part = Components::Part.new(type: "pt",
356
356
  value: part_num)
357
- identifier.number = Components::Code.new(value: "#{first_num.value}-#{second_num.value}")
357
+ identifier.number = "#{first_num.value}-#{second_num.value}"
358
358
  else
359
359
  # For GCR and others, include part number in compound number
360
360
  compound_value = "#{first_num.value}-#{second_num.value}"
361
361
  compound_value += "-#{part_num}" if part_num
362
- identifier.number = Components::Code.new(value: compound_value)
362
+ identifier.number = compound_value
363
363
  end
364
364
  else
365
365
  # No second_num, use first_num directly
366
- identifier.number = first_num
366
+ identifier.number = first_num.value.to_s
367
367
  end
368
368
  end
369
369
 
@@ -479,3 +479,5 @@ module Pubid
479
479
  end
480
480
  end
481
481
  end
482
+
483
+ Pubid::Nist::Builder.prepend(Pubid::Builder::AllPartsWrap)
@@ -28,6 +28,8 @@ module Pubid
28
28
  # Edition.new(type: "r", id: "5").to_s # => "r5"
29
29
  # Edition.new(type: "r", id: "5", original_prefix: " Rev. ").to_s # => "Rev. 5"
30
30
  class Edition < Lutaml::Model::Serializable
31
+ include ::Pubid::SubsetMatch
32
+
31
33
  attribute :type, :string # "-", "e", or "r"
32
34
  attribute :id, :string # Edition ID (number or year)
33
35
  attribute :additional_text, :string # Text after "rev" (WITHOUT "rev" prefix)
@@ -8,6 +8,8 @@ module Pubid
8
8
  # IssueNumber component for NIST identifiers
9
9
  # Represents the issue/number designation (e.g., "No. 12" in "Vol. 6, No. 12")
10
10
  class IssueNumber < Lutaml::Model::Serializable
11
+ include ::Pubid::SubsetMatch
12
+
11
13
  attribute :number, :string
12
14
 
13
15
  # Short form rendering: "n12"
@@ -23,6 +23,8 @@ module Pubid
23
23
  # - SP: Part number (pt1)
24
24
  # - Letter suffixes (A, B, C, etc.)
25
25
  class Part < Lutaml::Model::Serializable
26
+ include ::Pubid::SubsetMatch
27
+
26
28
  attribute :type, :string # "pt" for part notation, "n" for issue, "" for letter suffix
27
29
  attribute :value, :string # Part number or letter (1, 2, A, B, etc.)
28
30
 
@@ -13,6 +13,8 @@ module Pubid
13
13
  # Stage.new(id: "i", type: "pd").to_s(:short) # => "ipd"
14
14
  # Stage.new(id: "f", type: "pd").to_s(:long) # => "(Final Public Draft)"
15
15
  class Stage < Lutaml::Model::Serializable
16
+ include ::Pubid::SubsetMatch
17
+
16
18
  attribute :id, :string # i, f, 1-9
17
19
  attribute :type, :string # pd, wd, prd
18
20
 
@@ -15,6 +15,8 @@ module Pubid
15
15
  # Supplement.new(month: "Jan", year: "1924").to_s(:short) # => "supJan1924"
16
16
  # Supplement.new(has_revision: true).to_s(:short) # => "suprev"
17
17
  class Supplement < Lutaml::Model::Serializable
18
+ include ::Pubid::SubsetMatch
19
+
18
20
  attribute :number, :string # Supplement number (e.g., "2" in "supp2")
19
21
  attribute :year, :string # Year (4 digits); range START year
20
22
  attribute :month, :string # Month abbreviation; range START month
@@ -13,6 +13,8 @@ module Pubid
13
13
  # Translation.new(code: "por").to_s(:mr) # => ".por"
14
14
  # Translation.new(code: "ind").to_s(:short) # => " ind"
15
15
  class Translation < Lutaml::Model::Serializable
16
+ include ::Pubid::SubsetMatch
17
+
16
18
  attribute :code, :string # 3-letter ISO 639-2 code: spa, por, ind, etc.
17
19
 
18
20
  # Backward compatibility: language method returns code
@@ -15,6 +15,8 @@ module Pubid
15
15
  # Update.new(number: 1, year: 2021, month: 2).to_s(:mr) # => "-upd1-202102"
16
16
  # Update.new(number: 1, prefix: "dash").to_s(:short) # => "-upd1" (preserves original prefix)
17
17
  class Update < Lutaml::Model::Serializable
18
+ include ::Pubid::SubsetMatch
19
+
18
20
  attribute :number, :string # Update number as string
19
21
  attribute :year, :string # Year (4 digits as string)
20
22
  attribute :month, :string # Month (01-12 as string, optional)
@@ -12,6 +12,8 @@ module Pubid
12
12
  # Version.new(value: "1.0.2").to_s(:short) # => "ver1.0.2"
13
13
  # Version.new(value: "2.0").to_s(:long) # => "Version 2.0"
14
14
  class Version < Lutaml::Model::Serializable
15
+ include ::Pubid::SubsetMatch
16
+
15
17
  attribute :value, :string # Dotted notation: "1.0.2"
16
18
 
17
19
  # Render version in specified format
@@ -16,6 +16,8 @@ module Pubid
16
16
  # - CSM (Commercial Standards Monthly): Volume 6, Issue 1
17
17
  # - CIRC (Circular): Volume 539
18
18
  class Volume < Lutaml::Model::Serializable
19
+ include ::Pubid::SubsetMatch
20
+
19
21
  attribute :value, :string
20
22
 
21
23
  def to_s
@@ -27,7 +27,7 @@ module Pubid
27
27
  # bare { "value" => … } with no part;
28
28
  # - drop the redundant build artifacts, decomposed pieces of the
29
29
  # canonical number that identity/rendering never read.
30
- COMPACT_FLAT_CODES = %w[series number subseries].freeze
30
+ COMPACT_FLAT_CODES = %w[series subseries].freeze
31
31
  COMPACT_FLAT_VALUES = %w[volume].freeze
32
32
  COMPACT_DROP_ATTRS = %w[first_number second_number].freeze
33
33
 
@@ -105,7 +105,14 @@ module Pubid
105
105
  # equality so a manually-built id (without subseries set) still equals a
106
106
  # parsed one.
107
107
  attribute :subseries, Components::Code
108
- attribute :number, Components::Code
108
+ # Plain :string. NIST never populated the Code beyond `value`: `subpart`
109
+ # is set nowhere, and `part` was a reader splitting `value` on "-" that
110
+ # no library code called. `series`, `subseries`, `first_number`,
111
+ # `second_number` and `parts` keep the component.
112
+ attribute :number, :string
113
+ # NIST never uses `subpart`; declare it so the inherited Components::Code
114
+ # does not disagree with this class (the determinism landmine).
115
+ attribute :subpart, :string
109
116
 
110
117
  # V2 COMPONENTS (Lutaml::Model objects) - PROPER SEPARATION
111
118
  # `edition` (Components::Edition: type + id + additional_text) is the
@@ -216,6 +223,30 @@ module Pubid
216
223
 
217
224
  alias eql? ==
218
225
 
226
+ # A subset match skips the same attributes: `to_hash` drops the build
227
+ # artifacts, so an index row rebuilt by `from_hash` never has them.
228
+ def self.subset_ignored_attributes
229
+ EQUALITY_IGNORED_ATTRS
230
+ end
231
+
232
+ # `edition` is the primary edition/revision carrier and is already in
233
+ # the default %i[date year edition version] list. `update`/
234
+ # `update_component` (Components::Update: number+year+month) is a
235
+ # SEPARATE, currently-live discriminator the default list misses — the
236
+ # Letter Circular / Circular "rJun1992"-style revision parses into it,
237
+ # not into `edition` (see Builder#build_dated_identifier), so e.g.
238
+ # "NBS LC 800 rJun1992" and "NBS LC 800 rJul1995" failed to collapse
239
+ # under #to_all_parts/#=== before this override. (`edition_year` and
240
+ # `revision_year`/`revision_month` are NOT added here: the builder
241
+ # always sets `edition_year` alongside the real `edition` component
242
+ # (never alone), and `revision_year`/`revision_month` are transient —
243
+ # converted into `update`/`update_component` and cleared to nil before
244
+ # the object is returned — so neither carries live information outside
245
+ # what `edition`/`update` already do.)
246
+ def self.all_parts_edition_keys
247
+ super + %i[update update_component]
248
+ end
249
+
219
250
  def hash
220
251
  vals = self.class.attributes.each_key.reject do |name|
221
252
  EQUALITY_IGNORED_ATTRS.include?(name)
@@ -406,13 +437,11 @@ module Pubid
406
437
  match ? match[1].to_i : nil
407
438
  end
408
439
 
409
- public
410
-
411
440
  def to_full_style
412
441
  # "National Institute of Standards and Technology Special Publication 800-27, Revision A"
413
442
  result = publisher_full_name
414
443
  result += " #{series_full_name}" if series
415
- result += " #{number.value}" if number
444
+ result += " #{number}" if number
416
445
  result += parts.map { |p| "-#{p}" }.join if parts&.any?
417
446
 
418
447
  # Render volume and issue number in long form: "Vol. 6, No. 12"
@@ -739,6 +768,5 @@ module Pubid
739
768
  "NIST"
740
769
  end
741
770
  end
742
-
743
771
  end
744
772
  end
@@ -44,7 +44,7 @@ module Pubid
44
44
  result += " CS-E"
45
45
 
46
46
  # Number (already extracted from e104 → 104 in builder)
47
- result += " #{number.value}" if number
47
+ result += " #{number}" if number
48
48
 
49
49
  # Edition (e1943 for e104-43 pattern) plus any other component the
50
50
  # parser attached (volume, supplement, version, ...).
@@ -46,7 +46,7 @@ module Pubid
46
46
  def normalized_number
47
47
  return nil unless number
48
48
 
49
- num_value = number.value.to_s
49
+ num_value = number.to_s
50
50
 
51
51
  # Pattern: c4-4 → 4-4 (hide 'c' prefix)
52
52
  case num_value
@@ -64,12 +64,12 @@ module Pubid
64
64
  end
65
65
 
66
66
  # Override number to return normalized value
67
- # Tests expect number.value to be normalized (c4-4 → 4-4, 4-m-5 → 4-M-5)
67
+ # Tests expect number to be normalized (c4-4 → 4-4, 4-m-5 → 4-M-5)
68
68
  def number
69
69
  num = super
70
70
  return num unless num
71
71
 
72
- num_value = num.value.to_s
72
+ num_value = num.to_s
73
73
 
74
74
  # Pattern: c4-4 → 4-4 (hide 'c' prefix)
75
75
  case num_value
@@ -83,8 +83,7 @@ module Pubid
83
83
  num_value = num_value.gsub(/^m-/, "M-")
84
84
  end
85
85
 
86
- # Return new Code object with normalized value
87
- Components::Code.new(value: num_value)
86
+ num_value
88
87
  end
89
88
 
90
89
  def to_s(format = nil)
@@ -106,7 +105,7 @@ module Pubid
106
105
 
107
106
  if number
108
107
  # Normalize number value for CRPL patterns
109
- num_value = number.value.to_s
108
+ num_value = number.to_s
110
109
 
111
110
  # Pattern: c4-4 → 4-4 (hide 'c' prefix)
112
111
  case num_value
@@ -66,14 +66,14 @@ module Pubid
66
66
  # Convert dash-year patterns to edition format for FIPS
67
67
  # Pattern: "14-1971" → "14e1971" (edition year format)
68
68
  # But preserve dash for parts: "140-3" stays as-is
69
- if number.value =~ /^(\d{1,3})-(\d{4})$/
69
+ if number =~ /^(\d{1,3})-(\d{4})$/
70
70
  # This is a number-year pattern (e.g., "14-1971")
71
71
  # Convert to edition format: "14e1971"
72
72
  number_part = $1
73
73
  year_part = $2
74
74
  result += " #{number_part}e#{year_part}"
75
75
  else
76
- result += " #{number.value}"
76
+ result += " #{number}"
77
77
  end
78
78
  end
79
79
 
@@ -57,14 +57,14 @@ module Pubid
57
57
 
58
58
  def to_short_style
59
59
  result = "#{default_publisher} #{series_code}"
60
- result += " #{number.value}" if number
60
+ result += " #{number}" if number
61
61
  result += append_short_components
62
62
  result
63
63
  end
64
64
 
65
65
  def to_mr_style
66
66
  result = "#{default_publisher}.#{series_code}"
67
- result += ".#{number.value}" if number
67
+ result += ".#{number}" if number
68
68
  result += append_mr_components
69
69
  result
70
70
  end
@@ -13,8 +13,20 @@ module Pubid
13
13
  # feeds the cleaned string to the Parslet grammar and stamps the
14
14
  # detected format onto the parse tree.
15
15
  def self.class_parse_with_preprocessing(input)
16
+ # The shared Grammar strips a trailing "(all parts)", but the NIST
17
+ # preprocessor runs first and would swallow it — strip before it and
18
+ # carry the marker to the tree like Grammar#parse does.
19
+ all_parts = input.is_a?(String) && input.match?(::Pubid::Parser::Grammar::ALL_PARTS_SUFFIX)
20
+ input = input.sub(::Pubid::Parser::Grammar::ALL_PARTS_SUFFIX, "") if all_parts
16
21
  result = Preprocessor.new(input).call
17
22
  parsed = new.parse(result.cleaned)
23
+ if all_parts
24
+ parsed = case parsed
25
+ when Hash then parsed.merge(all_parts: true)
26
+ when Array then parsed.map { |h| h.merge(all_parts: true) }
27
+ else parsed
28
+ end
29
+ end
18
30
 
19
31
  if parsed.is_a?(Hash)
20
32
  parsed.merge(parsed_format: result.format)
@@ -651,8 +663,9 @@ module Pubid
651
663
  rule(:mr_identifier) do
652
664
  hash_prefix.maybe >>
653
665
  publisher >> dot >>
654
- simple_series >> dot >>
655
- report_number >>
666
+ # The catalogue also lists bare series identities with no
667
+ # report number ("NBS.CIRC").
668
+ simple_series >> dot.maybe >> report_number.maybe >>
656
669
  # Edition with underscore separator (MR format: 1648_2009)
657
670
  (str("_") >> digits.as(:edition_year)).maybe >>
658
671
  # Support letter suffix before update (e.g., 8286C-upd1) - Session 219