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
@@ -115,26 +115,33 @@ module Pubid
115
115
  parts << id.draft_status if id.draft_status
116
116
 
117
117
  # Type - only render for IEEE/AIEE publishers, and only for non-projects.
118
- # An unapproved draft is not yet a standard, so per IEEE guidance the
119
- # "Std" token is dropped from the type when draft_status is "Unapproved"
120
- # (e.g. "Draft Std" -> "Draft", "Std" -> "").
118
+ # A status word ("Unapproved") already marks the document as a draft,
119
+ # so the WHOLE type word is dropped — not just "Std" (pubid#318: the
120
+ # half-strip left "Draft" in the render, the re-parse dropped it, and
121
+ # to_s needed three rounds to converge while losing the type).
121
122
  should_render_type = id.publisher&.match?(/^(IEEE|AIEE)/)
122
123
 
123
- if should_render_type && !id.typed_stage&.project_status && id.type && !id.type.to_s.strip.empty? && id.type != "P"
124
+ if should_render_type && !id.typed_stage&.project_status && id.type && !id.type.to_s.strip.empty? && id.type != "P" &&
125
+ !id.draft_status.to_s.match?(/unapproved/i)
124
126
  type_str = id.type.dup
125
127
  type_str = type_str.sub(/^P/, "") if type_str.start_with?("P")
126
- if id.draft_status.to_s.match?(/unapproved/i)
127
- type_str = type_str.gsub(/\bStd\b/i, "").squeeze(" ").strip
128
- end
129
128
  parts << type_str unless type_str.strip.empty?
130
129
  end
131
130
 
132
- # Code - with P prefix for projects (concatenated, not separated)
131
+ # Code - with P prefix for projects (concatenated, not separated).
132
+ # IEEE semantics (normative): P = project = the document is a
133
+ # draft; no P = it has become a standard. The P-state is
134
+ # identity-bearing, so the renderer never adds or drops it —
135
+ # whatever the source spelling carries round-trips.
133
136
  if id.code_obj
134
137
  result = id.code_obj.to_s
135
138
 
136
- # Prepend P if this is a project AND code doesn't already have P
137
- if id.typed_stage&.project_status && should_render_type && !result.start_with?("P")
139
+ # Prepend P if this is a project AND code doesn't already have P.
140
+ # A recorded project marker (the source spelled the P on a
141
+ # non-IEEE-led publisher) prints regardless of the publisher.
142
+ if (id.typed_stage&.project_status && should_render_type ||
143
+ id.is_a?(Identifiers::ProjectDraftIdentifier) &&
144
+ id.project_marker) && !result.start_with?("P")
138
145
  result = "P#{result}"
139
146
  end
140
147
 
@@ -143,6 +150,7 @@ module Pubid
143
150
  result += mark(id.code_obj.number, id.code_obj.prefix,
144
151
  publishers: publishers_of(id))
145
152
 
153
+
146
154
  # Only attach year to code if there's no edition, no month, and no draft
147
155
  result += "-#{id.year}" if id.year && !id.draft_obj && !id.edition && !id.month
148
156
 
@@ -151,9 +159,29 @@ module Pubid
151
159
  # ("P802.16Rev2/D3"). Keeps a revision distinct from its base standard.
152
160
  result += "Rev#{id.revision}" if id.revision
153
161
 
154
- # Append draft to code - with or without space based on original format
162
+ # Append draft to code - with or without space based on original
163
+ # format. An exactly-IEC/IEEE co-published reference prints only the
164
+ # draft DESIGNATOR - a comma-separated project date ("IEC/IEEE
165
+ # 61886-1/D2, May 2020" prints as "…/D2") is catalogue metadata, not
166
+ # identity. Wider sets ("IEC/ISO/IEEE P82079-1/D1, June 2016") print
167
+ # the date; a space-separated date always stays ("…/DCDV July 2015").
168
+ # IEEE-published drafts normalize the comma date to the long form
169
+ # ("D08, September, 2018").
155
170
  if id.draft_obj
156
- result += id.space_before_draft ? " #{id.draft_obj}" : id.draft_obj.to_s
171
+ printed = id.draft_obj.to_s
172
+ if id.publisher == "IEC" && id.copublisher == ["IEEE"]
173
+ printed = printed.split(", ").first
174
+ elsif id.publisher == "IEEE" && id.draft_status.to_s.empty?
175
+ # The long comma form is for dated project drafts; an
176
+ # unapproved-draft render keeps its pinned single-comma form
177
+ # (pubid#318 idempotence).
178
+ printed = printed.sub(/, ([A-Z][a-z]+) (\d{4})\z/, ', \1, \2')
179
+ end
180
+ # A space-separated draft year with no print date trailing is
181
+ # IEEE's dash form ("…/D1-2006"); with a trailing print date the
182
+ # space stays ("…/D1 2004, Nov 2004").
183
+ printed = printed.sub(/\A(D\d+) (\d{4})\z/, '\1-\2')
184
+ result += id.space_before_draft ? " #{printed}" : printed
157
185
  end
158
186
 
159
187
  # Append interpretation notation (/INT)
@@ -191,8 +219,12 @@ module Pubid
191
219
  # Build the main identifier (without month yet)
192
220
  result = parts.join(" ")
193
221
 
194
- # Month/Day - append directly to avoid extra space before comma
195
- if id.month
222
+
223
+ # Month/Day - append directly to avoid extra space before comma.
224
+ # An exactly-IEC/IEEE co-published reference drops the comma date
225
+ # entirely (same catalogue-metadata rule as the draft date above):
226
+ # "IEC/IEEE P60076-16, May 2016" prints as "IEC/IEEE 60076-16".
227
+ if id.month && !(id.publisher == "IEC" && id.copublisher == ["IEEE"])
196
228
  result += ", #{id.month}"
197
229
  result += " #{id.day}" if id.day
198
230
  if id.year && !id.edition
@@ -127,6 +127,10 @@ module Pubid
127
127
  end
128
128
 
129
129
  def publisher_component
130
+ # The IEC/IEEE co-published pair is definitional to the class and
131
+ # was previously lost ("urn:ieee:ieee" — no number, no draft).
132
+ return "iec-ieee" if identifier.is_a?(Identifiers::IecIeeeCopublished)
133
+
130
134
  pub = normalized_publisher
131
135
 
132
136
  if identifier.copublisher&.any?
@@ -150,6 +154,14 @@ module Pubid
150
154
  end
151
155
 
152
156
  def type_component
157
+ # A joint stage draft carries the stage in its draft clause and the
158
+ # project P inside the code — a separate type segment would
159
+ # duplicate both.
160
+ if identifier.is_a?(Identifiers::JointDevelopment) &&
161
+ identifier.ieee_draft.to_s.start_with?("D=")
162
+ return nil
163
+ end
164
+
153
165
  return nil unless identifier.type
154
166
 
155
167
  type = identifier.type
@@ -159,6 +171,16 @@ module Pubid
159
171
  end
160
172
 
161
173
  def code_component
174
+ # The co-published code is number+parts+separators (no year — its
175
+ # own segment), e.g. "61886-1".
176
+ if identifier.is_a?(Identifiers::IecIeeeCopublished)
177
+ return nil if identifier.number.to_s.empty?
178
+
179
+ # Year-less: the publication year is its own URN segment (the
180
+ # rebuilt copublished_number would double-emit it).
181
+ return "#{identifier.number}#{identifier.parts_suffix}"
182
+ end
183
+
162
184
  return nil unless identifier.code_obj
163
185
 
164
186
  identifier.code_obj.to_s
@@ -182,6 +204,15 @@ module Pubid
182
204
  end
183
205
 
184
206
  def draft_component
207
+ if identifier.is_a?(Identifiers::JointDevelopment) &&
208
+ identifier.ieee_draft.to_s.start_with?("D=")
209
+ return "draft.#{identifier.ieee_draft}"
210
+ end
211
+
212
+ if identifier.is_a?(Identifiers::IecIeeeCopublished)
213
+ return identifier.draft_info ? "draft.#{identifier.draft_info}" : nil
214
+ end
215
+
185
216
  return nil unless identifier.draft_obj
186
217
 
187
218
  "draft.#{identifier.draft_obj}"
data/lib/pubid/ieee.rb CHANGED
@@ -20,6 +20,7 @@ module Pubid
20
20
  autoload :Parser, "#{__dir__}/ieee/parser"
21
21
  autoload :PreParser, "#{__dir__}/ieee/pre_parser"
22
22
  autoload :Compaction, "#{__dir__}/ieee/compaction"
23
+ autoload :ProjectRenderer, "#{__dir__}/ieee/project_renderer"
23
24
  autoload :Renderer, "#{__dir__}/ieee/renderer"
24
25
  autoload :TypedStages, "#{__dir__}/ieee/typed_stages"
25
26
  autoload :UrnGenerator, "#{__dir__}/ieee/urn_generator"
@@ -162,3 +163,4 @@ Pubid::Registry.register(:ieee, Pubid::Ieee)
162
163
  # Per-flavor format registry: inherits global formats, overrides :human
163
164
  Pubid::Ieee::Identifier.format_registry = Pubid::FormatRegistry.new(parent: Pubid::Identifier.format_registry)
164
165
  Pubid::Ieee::Identifier.format_registry.register(:human, renderer: Pubid::Ieee::Renderer)
166
+ Pubid::Ieee::Identifier.format_registry.register(:project, renderer: Pubid::Ieee::ProjectRenderer)
@@ -0,0 +1,7 @@
1
+ # IETF flavor notes
2
+
3
+ IETF readiness for the unified relaton index.
4
+
5
+ These notes were part of the root `CLAUDE.md`. Read them before you change `lib/pubid/ietf/` or `spec/pubid/ietf/`. The root file keeps the cross-flavor contract that every flavor obeys.
6
+
7
+ - **IETF readiness for the unified relaton index (`relaton-data-ietf`)**: relaton is collapsing RFCs, the RFC sub-series (BCP/STD/FYI) and Internet-Drafts into **one** index keyed on the structured pubid hash (`pubid_class: ::Pubid::Ietf::Identifier`). That index is **all-or-nothing** — `Relaton::Index::FileIO#deserialize_id` raises on the *first* unparseable row and `#load_index` then rejects the whole index — so one bad id out of 176,862 means no consumer resolves *any* IETF reference. Four changes, all verified against the published corpora (`relaton-data-{rfcs,rfcsubseries,ids}`, branch `v2`). **(1) `draft_rest` accepts `.` and `A-Z`** (`match['a-zA-Z0-9+_.\-']`): exactly 50 of the 166,740 published draft ids failed without them (21 dotted `draft-ietf-pilc-2.5g3g-12`, 29 uppercase `draft-chapin-clnp-ISO8473-00`, 0 both), and a character census confirms the class now covers the corpus **exactly** — the only chars outside `[a-z0-9-]` anywhere in it are `. _ +` and `A-Z`. Widening is safe because the top-level alternation is keyed on the leading token (`RFC`/`BCP|STD|FYI`/`draft-`), so a wider draft tail can never make another branch match; `Builder#split_draft_version` needed no change (it inspects only the last three characters). The set stays **closed** — a space, `/` or `&` in a slug is crawler junk and still fails (pinned by negative examples). **(2) Zero-padded ids parse, grammar-level**: `rfc-index.xml` writes sub-series membership as `<is-also><doc-id>STD0066</doc-id></is-also>` (369 padded doc-ids) and RFCs as `RFC0001`, and relaton#109 forbids relaton normalizing them itself. The space is `.maybe` on both the `rfc` and `subseries` rules and `Builder#unpad` strips the pad (`/\A0+(?=\d)/`, a look-ahead so an all-zero number keeps its last digit), so `STD0066`/`STD 0066`/`STD66` all render the canonical `STD 66`. `unpad` is applied unconditionally, so the **spaced** padded form is normalized too — `RFC 0001` previously parsed and rendered back verbatim, and now renders `RFC 1` with a correspondingly changed hash and URN. That is deliberate (one canonical spelling per document) and has **zero** effect on the published corpora, which contain no padded row. **This is a normalizing parse, so the padded spellings must NEVER go into the byte-exact `spec/fixtures/ietf/identifiers/pass/`** — they live in `spec/pubid/ietf/zero_padded_spec.rb`, the BIPM `CIPM/2005-06(REV)` precedent. **(3) The Internet-Draft slug moved from `name` into `number`, and `series` became derived**: relaton bsearches on `id.root.number.to_s`, so with the slug in `name` all 166,740 drafts keyed to `""` and the narrowing bought nothing (with the slug: 43,564 buckets, median 2, worst 101). `name` is **dropped entirely** (no alias); the slug — leading `draft-` included — is the key, deliberately shared by a draft's unversioned aggregator row and every one of its versions so a document and its variants cluster. `series` is likewise **not stored**: `_type: pubid:ietf:std` already encodes it, so `Bcp`/`Std`/`Fyi` expose a `SERIES` constant + a plain `#series` reader (safe — `::Pubid::Identifier` declares no `series` attribute and nothing generic reads `.series`) and `build_subseries` stops passing it. The whole serialized vocabulary is now **three keys**: `_type` + `number` always, `version` only on a versioned draft — a sub-series row (`{_type, number}`) has the same shape as an RFC's. **The attributes live on the five LEAVES, not the shared `Pubid::Ietf::Identifier`**, via the `Identifiers::Serialization` mixin (`number` + the `key_value` block; `InternetDraft` merges its own `version` mapping on top — lutaml combines a mixin-installed block with a second in-class one, the ITU precedent). This is the IEEE `number` determinism landmine: a `:string` redefinition of the parent's `Components::Code number` on a class the leaves *inherit* from resolves nondeterministically under multi-flavor load. It was invisible while drafts left `number` nil; it is load-bearing now. The base deliberately carries **no** `key_value` block — one there would be inherited-and-merged by every leaf. `spec/pubid/ietf/root_number_spec.rb` is the tripwire (asserts `number` is a `String`, the exact keys, the exact `to_hash` key sets) and **must run under the full `rake` suite** to mean anything. **(4) Round-trip is validated here because relaton structurally cannot**: `FileIO#id_supported?` **skips** its `to_hash`/`from_hash` check whenever the object is a concrete subclass (`return true unless obj.instance_of?(@pubid_class)`), and *every* IETF id is a subclass — so a lossy round-trip would pass validation silently and produce wrong lookups. Three layers: `fixtures_spec.rb` is now **zero-failure** (was a 90% threshold — wrong for an all-or-nothing index) and checks `to_s` byte-exactness, `from_hash(to_hash)` fidelity in both `to_s` and `to_hash`, **and** a non-empty `root.number` for every fixture line; the fixtures grew to ~2,500 lines (the complete 365-row sub-series corpus, a 139-id structural edge set, a deterministic 1-in-84 draft sample — each file's header records the regeneration command); and `spec/pubid/ietf/corpus_round_trip_spec.rb` runs the same four checks over all 176,862 published ids, **opt-in** via `PUBID_IETF_CORPUS=<dir with the three relaton-data checkouts> bundle exec rake test:corpus_ietf` (self-skips otherwise; ~100s; verified 0 failures). **relaton note — the indexes must be regenerated, there is no alias**: `name` and `series` are gone as serialized keys, so a pre-`number` row (`{_type, name, version}`) deserializes with **`number` nil** and no error at all — lutaml ignores unknown keys, and relaton's subclass skip means `id_supported?` won't catch it either, leaving every draft keyed under `""`. Rendering is therefore the loud failure: `Renderer#require_number!` raises `ArgumentError` on a nil/empty `number` rather than returning `nil` or `"-02"`, and the renderer's sub-series arm is an explicit `when Bcp, Std, Fyi` with an `else raise` instead of a catch-all (a bare `Pubid::Ietf::Identifier` has no `series` reader). Pinned by the "legacy `name`-shaped hash" block in `root_number_spec.rb`. **Pre-existing bug fixed in passing**: `UrnParser`'s bare `Errors::ParseError` does not resolve — `Errors` lives under `Pubid::UrnParser` (the module), which is neither in the lexical scope of `Pubid::Ietf::UrnParser` nor an ancestor of `Base` — so an invalid URN raised `NameError` instead, and the spec's `raise_error(StandardError)` accepted it. Now fully qualified as `Pubid::UrnParser::Errors::ParseError` (the adobe/easc/gost/iala convention) with the spec asserting that exact class. **`Pubid::Iec::UrnParser` has the identical bug and is untouched here.** Also: `Identifier.parse` gained the inline `MAX_INPUT_LENGTH` guard — relaton reaches that class-level funnel directly through `pubid_class:`, bypassing `Pubid::Ietf.parse` — and IETF was added to the three cross-flavor tables it was missing from (`identifier_roundtrip_spec`, `uniform_identifier_handle_spec`, `redos_guard_spec`), which is what exercises the leaf `number` under real multi-flavor load. (hand-off: ietf-index-readiness.)
@@ -76,3 +76,5 @@ module Pubid
76
76
  end
77
77
  end
78
78
  end
79
+
80
+ Pubid::Ietf::Builder.prepend(Pubid::Builder::AllPartsWrap)
@@ -35,3 +35,5 @@ module Pubid
35
35
  end
36
36
  end
37
37
  end
38
+
39
+ Pubid::Iho::Builder.prepend(Pubid::Builder::AllPartsWrap)
@@ -42,3 +42,5 @@ module Pubid
42
42
  end
43
43
  end
44
44
  end
45
+
46
+ Pubid::Isbn::Builder.prepend(Pubid::Builder::AllPartsWrap)
@@ -0,0 +1,47 @@
1
+ # ISO flavor notes
2
+
3
+ - **No identifier attribute holds a `Components::Code` any more**: `number`,
4
+ `part` and `subpart` on `Pubid::Iso::Identifier`, the committee structure on
5
+ `Identifiers::TcDocument` (`tc_type`/`tc_number`/`sc_type`/`sc_number`/
6
+ `wg_type`/`wg_number`), `Identifiers::Directives#subgroup` and the
7
+ `edition.number` the builder writes are all plain strings, and
8
+ `Pubid::Iso::Components::Code` is deleted.
9
+ **The component was degenerate everywhere it was used.** Measured over the
10
+ whole 7,613-id pass corpus: no ISO Code carries `prefix`, `part`, `subpart`
11
+ or `parts`, `parts` is never written anywhere in `lib/pubid/iso`, and no Code
12
+ renders differently from its own `value`. ISO's part and subpart are sibling
13
+ attributes of the identifier, not fields of the number — the `-` join in
14
+ `Code#to_s` was unreachable.
15
+ **`edition.number` was worse than degenerate: it leaked a live object.**
16
+ `Components::Edition#number` is typed `Lutaml::Model::Type::Value`, so lutaml
17
+ never serialized it — `to_hash` returned the `Components::Code` instance
18
+ itself, and `to_yaml` emitted `!ruby/object:Pubid::Components::Code` with its
19
+ internal ivars, which `YAML.safe_load` refuses. An edition now serializes
20
+ `{"number" => "13", "original_text" => "Ed 13"}`.
21
+ **The TC converters are gone with it**: twelve `*_to_kv`/`*_from_kv` helpers
22
+ were replaced by plain `map "tc_type", to: :tc_type` declarations (the
23
+ ETSI/OIML shape), because a String needs no converter to flatten.
24
+ **Two shared surfaces needed a change, and both are the documented shims**:
25
+ `Renderers::DirectivesRenderer` called `subgroup.render(context:)` directly
26
+ and now reads through `render_component`; `Pubid::Iso.build_code` returned a
27
+ Code and now returns the string (ASTM's `to_iso_identifier` goes through it,
28
+ which is how one ASTM example caught the `NameError` after the class was
29
+ deleted).
30
+ **Verification — replay a baseline, do not trust the suite alone.** Against
31
+ all 7,621 pass-fixture ids captured on the parent commit, `to_s`, `to_urn`,
32
+ `to_mr_string`, `root.number` and the identifier class are **byte-identical**,
33
+ and `to_hash` differs for exactly **12** ids: the 9 `subgroup` rows and the 3
34
+ editions below. The first replay also caught a real regression the specs did
35
+ not — `directives.rb` still called `part.value.downcase`, so 4 Directives
36
+ URNs raised.
37
+ **relaton note — `relaton-data-iso` needs a re-crawl, and only for one key.**
38
+ `subgroup` flattens from `{"value" => "JTC 1"}` to `"JTC 1"`, and the
39
+ published `index-v2.yaml` (branch `v2`) carries **5 such rows**, all ISO/IEC
40
+ JTC 1 directives. **No compatibility shim is kept** — a stored nested
41
+ `subgroup` will not deserialize. The edition change reaches nothing stored:
42
+ `grep -c edition` over that index returns **0**.
43
+ **Spec fallout is the shape to expect for the remaining flavors**: 592
44
+ assertions across 25 files read `.number.value` / `.part.value`. The rewrite
45
+ needs a lookbehind, because `tc_number.value` and `edition.number.value` are
46
+ different attributes — the first pass stripped `edition.number.value` too,
47
+ and two corrigendum examples caught it.
@@ -58,6 +58,12 @@ module Pubid
58
58
  end
59
59
 
60
60
  def build(parsed_hash)
61
+ # "(all parts)" and the ":ser" URN name every part of the document,
62
+ # so they build an AllPartsIdentifier around the document (the URN
63
+ # parser puts the key on the outermost identifier for the same
64
+ # reason). The document itself holds no all-parts mark.
65
+ all_parts = parsed_hash.delete(:all_parts)
66
+
61
67
  # For ISO/R legacy format, split into publisher and type
62
68
  if parsed_hash[:iso_r_prefix]
63
69
  parsed_hash[:publisher] = "ISO"
@@ -77,14 +83,20 @@ module Pubid
77
83
  end
78
84
  end
79
85
 
80
- # Instantiate the identifier based on the typed stage
81
- identifier = locate_identifier_klass(parsed_hash).new
82
-
83
- # For French GUIDE entries: "Guide ISO/CEI 37:1995"
86
+ # For French GUIDE entries: "Guide ISO/CEI 37:1995". The rename
87
+ # must happen BEFORE class selection — locate_identifier_klass
88
+ # reads :type_with_stage, and the tree of a guide-first spelling
89
+ # carries an empty :type_with_stage plus the type under
90
+ # :type_with_stage_fr, so deferring the rename selected the
91
+ # default International Standard class for every guide-first
92
+ # (and Cyrillic "Руководства ИСО …") reference.
84
93
  if type_with_stage_fr = parsed_hash.delete(:type_with_stage_fr)
85
94
  parsed_hash[:type_with_stage] = type_with_stage_fr
86
95
  end
87
96
 
97
+ # Instantiate the identifier based on the typed stage
98
+ identifier = locate_identifier_klass(parsed_hash).new
99
+
88
100
  # For DirectivesSupplement, rename :publisher to :supplement_publisher
89
101
  if identifier.is_a?(Identifiers::DirectivesSupplement) && parsed_hash[:publisher]
90
102
  parsed_hash[:supplement_publisher] = parsed_hash.delete(:publisher)
@@ -112,7 +124,7 @@ module Pubid
112
124
  identifier.type = default_typed_stage.to_type
113
125
  end
114
126
 
115
- identifier
127
+ all_parts ? identifier.to_all_parts : identifier
116
128
  end
117
129
 
118
130
  def handle_key(identifier, key, value)
@@ -143,7 +155,7 @@ module Pubid
143
155
  return nil unless year_value.between?(1900, 2099)
144
156
 
145
157
  {
146
- number: code_class.new(value: number),
158
+ number: number,
147
159
  date: ::Pubid::Components::Date.new(year: part),
148
160
  }
149
161
  end
@@ -197,7 +209,7 @@ module Pubid
197
209
  # or "105/F" ('F' is part)
198
210
  # or "5843/6" ('6' is part)
199
211
  # LEGACY: "4037-1979" (number-year, year should become date)
200
- parse_number_with_part(value, code_class: Pubid::Iso::Components::Code)
212
+ parse_number_with_part(value)
201
213
 
202
214
  when :directives_type
203
215
  # nothing to do here, just return nil
@@ -240,8 +252,10 @@ module Pubid
240
252
  original_text = value.to_s
241
253
  # Extract just the digit(s) for the number field
242
254
  number_string = original_text.match(/\d+/)&.to_s
243
- number_code = number_string ? Pubid::Iso::Components::Code.new(value: number_string) : nil
244
- Pubid::Components::Edition.new(number: number_code,
255
+ # A plain string: Components::Edition#number is typed Value, so a
256
+ # component here would leak a live Ruby object into to_hash (and a
257
+ # !ruby/object tag into to_yaml).
258
+ Pubid::Components::Edition.new(number: number_string,
245
259
  original_text: original_text)
246
260
 
247
261
  when :languages
@@ -260,8 +274,7 @@ module Pubid
260
274
 
261
275
  when :subgroup
262
276
  # Handle JTC 1 subgroup in directives (ISO/IEC JTC 1 DIR)
263
- # Store as a component for potential use in rendering
264
- Pubid::Iso::Components::Code.new(value: value.to_s)
277
+ value.to_s
265
278
 
266
279
  when :supplements
267
280
  # Handle bundled supplements (+ operator)
@@ -274,12 +287,10 @@ module Pubid
274
287
 
275
288
  # TC Document attributes
276
289
  when :tc_type, :sc_type, :wg_type
277
- # TC, SC, WG types are code components
278
- Pubid::Iso::Components::Code.new(value: value.to_s)
290
+ value.to_s
279
291
 
280
292
  when :tc_number, :sc_number, :wg_number
281
- # TC, SC, WG numbers are code components
282
- Pubid::Iso::Components::Code.new(value: value.to_s)
293
+ value.to_s
283
294
 
284
295
  when :year
285
296
  # For TC documents with year, convert to Date
@@ -290,7 +301,7 @@ module Pubid
290
301
  # For regular identifiers, this is handled in :number_with_part
291
302
  if value.is_a?(Parslet::Slice) || value.is_a?(String) ||
292
303
  value.is_a?(Integer)
293
- Pubid::Iso::Components::Code.new(value: value.to_s)
304
+ value.to_s
294
305
  else
295
306
  value
296
307
  end
@@ -314,3 +325,5 @@ module Pubid
314
325
  end
315
326
  end
316
327
  end
328
+
329
+ Pubid::Iso::Builder.prepend(Pubid::Builder::AllPartsWrap)
@@ -8,6 +8,8 @@ module Pubid
8
8
  # ISO Publisher with copublisher support
9
9
  # Examples: ISO, ISO/IEC, ISO/IEC/IEEE
10
10
  class Publisher < Lutaml::Model::Serializable
11
+ include ::Pubid::SubsetMatch
12
+
11
13
  attribute :publisher, :string, default: -> { "ISO" }
12
14
  attribute :copublisher, :string, collection: true
13
15
 
@@ -3,7 +3,6 @@
3
3
  module Pubid
4
4
  module Iso
5
5
  module Components
6
- autoload :Code, "#{__dir__}/components/code"
7
6
  autoload :Publisher, "#{__dir__}/components/publisher"
8
7
  end
9
8
  end
@@ -11,6 +11,12 @@ module Pubid
11
11
  attribute :copublishers, ::Pubid::Iso::Components::Publisher,
12
12
  collection: true
13
13
 
14
+ # ISO prints "(all parts)" and has a series URN, so it has its own
15
+ # all-parts class.
16
+ def self.all_parts_class
17
+ Identifiers::AllParts
18
+ end
19
+
14
20
  # The publisher implied when none is serialized. ISO for most types;
15
21
  # publisher-less types (IWA) override this to nil.
16
22
  def self.default_publisher
@@ -56,9 +62,14 @@ module Pubid
56
62
  ts ? dup.tap { |id| id.typed_stage = ts } : dup
57
63
  end
58
64
 
59
- attribute :number, ::Pubid::Iso::Components::Code
60
- attribute :part, ::Pubid::Iso::Components::Code
61
- attribute :subpart, ::Pubid::Iso::Components::Code
65
+ # Plain strings. Iso::Components::Code existed to join `parts` with
66
+ # "-", a composition no ISO identifier ever reached: `parts` is never
67
+ # written in lib/pubid/iso and no Code in the 7,613-id corpus carries
68
+ # anything but `value`. ISO part and subpart are sibling attributes, not
69
+ # fields of the number.
70
+ attribute :number, :string
71
+ attribute :part, :string
72
+ attribute :subpart, :string
62
73
 
63
74
  # Polymorphic type map for lutaml::Model key_value serialization
64
75
  # Maps polymorphic_name → class name for deserialization
@@ -132,20 +143,6 @@ module Pubid
132
143
  # unique typed-stage `code` under "stage" and recompute the rest on
133
144
  # load. _type already pins the document type.
134
145
  map "stage", with: { to: :stage_to_kv, from: :stage_from_kv }
135
- # Omit the `false` default; only the meaningful `true` is serialized.
136
- map "all_parts", with: { to: :all_parts_to_kv, from: :all_parts_from_kv }
137
- end
138
-
139
- def all_parts_to_kv(model, doc)
140
- return unless model.all_parts
141
-
142
- doc.add_child(
143
- Lutaml::KeyValue::DataModel::Element.new("all_parts", true),
144
- )
145
- end
146
-
147
- def all_parts_from_kv(model, value)
148
- model.all_parts = value
149
146
  end
150
147
 
151
148
  # Serialize typed_stage as just its unique code (e.g. "is", "dis",
@@ -213,8 +210,10 @@ module Pubid
213
210
  doc.add_child(Lutaml::KeyValue::DataModel::Element.new(key, v.to_s))
214
211
  end
215
212
 
213
+ # number/part/subpart are plain strings; the converter name is kept
214
+ # so the key_value map below does not have to change.
216
215
  def build_code(value)
217
- ::Pubid::Iso::Components::Code.new(value: value.to_s)
216
+ value.to_s
218
217
  end
219
218
 
220
219
  # --- date serialized flat as year/month/day ---
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Iso
5
+ module Identifiers
6
+ # Every part of one ISO document: "ISO 9000 (all parts)".
7
+ #
8
+ # The URN is the series URN: the document URN without its stage, plus
9
+ # the "ser" slot.
10
+ class AllParts < ::Pubid::Iso::Identifier
11
+ include ::Pubid::AllParts
12
+
13
+ def to_urn
14
+ "#{identity.exclude(:stage, :typed_stage).to_urn}:ser"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -4,7 +4,10 @@ module Pubid
4
4
  module Iso
5
5
  module Identifiers
6
6
  class Directives < SingleIdentifier
7
- attribute :subgroup, ::Pubid::Components::Code
7
+ # A plain string ("JTC 1"). The serialized form flattens from
8
+ # `{"value" => "JTC 1"}` to `"JTC 1"`, so the 5 directives rows of
9
+ # relaton-data-iso need a re-crawl; no compatibility shim is kept.
10
+ attribute :subgroup, :string
8
11
 
9
12
  # Merges with Identifier's block (base attributes); only adds the
10
13
  # Directives-specific subgroup so it survives to_hash/from_hash.
@@ -46,15 +49,15 @@ module Pubid
46
49
  parts << ([publisher] + copubs).map(&:body).map(&:downcase).join("-")
47
50
 
48
51
  if subgroup
49
- subgroup_parts = subgroup.value.split
52
+ subgroup_parts = subgroup.split
50
53
  parts << subgroup_parts[0].downcase if subgroup_parts[0]
51
54
  parts << subgroup_parts[1] if subgroup_parts[1]
52
55
  end
53
56
 
54
57
  parts << "dir"
55
58
 
56
- parts << number.value if number
57
- parts << part.value.downcase if part
59
+ parts << number.to_s if number
60
+ parts << part.to_s.downcase if part
58
61
  parts << date.year if date
59
62
 
60
63
  parts.join(":")
@@ -89,8 +89,10 @@ format: nil, stage_format_long: nil, with_date: nil, **opts)
89
89
  def to_supplement_s(lang: :en, lang_single: false, with_edition: false,
90
90
  format: nil, stage_format_long: nil, with_date: nil, **_opts)
91
91
  date_str = if date
92
- month_part = date.month ? "-#{date.month}" : ""
93
- ":#{date.render}#{month_part}"
92
+ # Components::Date#render already carries the month
93
+ # (and day) when present — appending a month_part
94
+ # here doubled it (":2016-05-05-05").
95
+ ":#{date.render}"
94
96
  else
95
97
  ""
96
98
  end