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
@@ -19,6 +19,8 @@ module Pubid
19
19
  # - Approved Draft: Board-approved but unpublished (D7-D9)
20
20
  # - Published: Final standards (no P prefix)
21
21
  class TypedStage < Lutaml::Model::Serializable
22
+ include ::Pubid::SubsetMatch
23
+
22
24
  # All recognized abbreviations for this stage
23
25
  # First in array is canonical abbreviation
24
26
  attribute :abbr, :string, collection: true
@@ -201,6 +201,18 @@ module Pubid
201
201
  hash
202
202
  end
203
203
 
204
+ # Three more IEEE discriminators the default %i[date year edition
205
+ # version] list misses, alongside `edition`/`year`: `revision` (e.g.
206
+ # "2" in "P802.16Rev2"); `reaffirmed` (the "(R2010)" year, read by
207
+ # renderer.rb and urn_generator.rb — two reaffirmations of the same
208
+ # standard failed to collapse under #to_all_parts/#===); and
209
+ # `edition_month`, the month half of "Edition N YYYY-MM"
210
+ # (renderer.rb's `"Edition #{edition} #{year}-#{edition_month}"`),
211
+ # which survived even though its sibling `year` is already stripped.
212
+ def self.all_parts_edition_keys
213
+ super + %i[revision reaffirmed edition_month]
214
+ end
215
+
204
216
  # Inverse of the to_hash compaction: expand a scalar `stage` back into the
205
217
  # `typed_stage` sub-hash (on a deep copy, recursively) before lutaml
206
218
  # deserializes, so nested bases rebuild their component too. `draft` needs
@@ -351,6 +363,14 @@ module Pubid
351
363
  def self.parse_single(input)
352
364
  # Apply legacy update_codes normalization first, before Parser's extensive preprocessing
353
365
  normalized = Core::UpdateCodes.apply(input, :ieee)
366
+ # UpdateCodes may introduce the "; " double-label separator
367
+ # ("ISO/IEC13210: 1994 (E) ANSI/IEEE Std ..." → the ISO/IEC and
368
+ # IEEE labels of one document), but the dispatch ran on the raw
369
+ # input - re-dispatch so the dual construction sees it.
370
+ if normalized != input && normalized.include?("; ")
371
+ result = PreParser.preprocess(normalized)
372
+ return build_dual(result.parts) if result.dispatch == :dual_semicolon
373
+ end
354
374
  parsed = Parser.parse(normalized) # Use class method for preprocessing
355
375
  builder = Builder.new(Identifier)
356
376
  # Pass the original input string to builder for context
@@ -26,6 +26,15 @@ module Pubid
26
26
  attribute :iec_year, :string # IEC year like "2013"
27
27
  attribute :date_info, :string # Date information like "(10/07)"
28
28
 
29
+ # `year_sep` only describes how a year is attached. When the reference
30
+ # omits the year, a subset match skips it: its default "-" would
31
+ # otherwise refuse a candidate printed with ":".
32
+ def subset_attribute_match?(name, mine, theirs)
33
+ return true if name == :year_sep && year.nil?
34
+
35
+ super
36
+ end
37
+
29
38
  # The printed IEC/IEEE number, rebuilt losslessly from the split
30
39
  # columns (the renderer/urn read this). Not stored — number/parts/
31
40
  # separators/year fully describe it, so the verbatim string never
@@ -42,6 +42,8 @@ module Pubid
42
42
  attribute :year, :string
43
43
  attribute :iso_stage, :string # For ISO stage if present
44
44
  attribute :ieee_draft, :string # For IEEE P/D notation if present
45
+ attribute :parenthetical_content, :string # e.g. the "(E)" edition
46
+ # marker of an ISO/IEC label
45
47
 
46
48
  # Accepts keyword args or a single positional hash (the base
47
49
  # #exclude/matching rebuild passes the latter) — see
@@ -138,14 +140,23 @@ module Pubid
138
140
  end
139
141
  end
140
142
 
141
- # Code (NO P prefix in ISO format, NO draft notation)
142
- code_str = code.to_s.gsub(/^P/, "")
143
+ # IEEE semantics: P = project (a draft); no P = a standard. The
144
+ # P-state is identity-bearing and prints as spelled — it is never
145
+ # added or stripped here.
146
+ code_str = code.to_s
143
147
  code_str += mark unless code_str.empty?
144
148
  parts << code_str if code_str && !code_str.empty?
145
149
 
146
- # Join with space and add year with colon
150
+ # Join with space and add year with colon; the joint stage-draft
151
+ # clause ("D=WD.5") rides after the year in the ISO-led print.
147
152
  result = parts.join(" ")
148
153
  result += ":#{year}" if year
154
+ result += "/#{ieee_draft}" if ieee_draft && ieee_draft.start_with?("D=")
155
+ # Only the language/edition marker ("(E)", "(E/F)") prints; a
156
+ # trailing relationship narrative is metadata, not identity.
157
+ if parenthetical_content&.match?(%r{\A[A-Z](?:\s*[/&]\s*[A-Z])*\z})
158
+ result += " (#{parenthetical_content})"
159
+ end
149
160
 
150
161
  result
151
162
  end
@@ -159,13 +170,9 @@ module Pubid
159
170
  # Publishers (slash-separated)
160
171
  parts << publishers.join("/") if publishers && !publishers.empty?
161
172
 
162
- # Build code part
163
- code_str = code.to_s.gsub(/^P/, "") # Remove any existing P first
164
-
165
- # Add P prefix for projects (IEEE format always shows P for drafts)
166
- if typed_stage&.project_status || type == "P"
167
- code_str = "P#{code_str}"
168
- end
173
+ # Build code part — the P-state prints as spelled (P = project
174
+ # draft; no P = standard). Never added or stripped.
175
+ code_str = code.to_s
169
176
 
170
177
  # Mark after the number, before the draft and the year
171
178
  code_str += mark unless code_str.empty?
@@ -33,10 +33,17 @@ module Pubid
33
33
  # object. The "P" is reflected in the typed_stage, not in the Code
34
34
  # component itself. Accepts keyword args or a single positional hash (the
35
35
  # base #exclude/matching rebuild passes the latter).
36
+ attribute :project_marker, :boolean, default: -> { false }
37
+
36
38
  def initialize(args = {}, **kwargs)
37
39
  args = kwargs.empty? ? args.dup : args.merge(kwargs)
38
- # If code is provided with "P" prefix (e.g., "P1234"), strip it
40
+ # If code is provided with "P" prefix (e.g., "P1234"), strip it.
41
+ # P = project (the document is a draft): the marker is
42
+ # identity-bearing, so its presence is remembered for the render —
43
+ # stripping the letter must not strip the fact (docs/
44
+ # IEEE-DRAFT-STAGES.md §1.1).
39
45
  if args[:code].is_a?(String) && args[:code].start_with?("P")
46
+ args[:project_marker] = true unless args.key?(:project_marker)
40
47
  args[:code] = args[:code][1..] # Strip leading "P"
41
48
  end
42
49
 
@@ -30,8 +30,11 @@ module Pubid
30
30
  # Format: "IEEE/ASTM SI 10-1997" (published)
31
31
  # or: "IEEE/ASTM PSI 10/D2, October 2015" (draft)
32
32
 
33
- # Use proper Draft component (Lutaml::Model object)
34
- attr_accessor :draft_obj
33
+ # NOTE: no `attr_accessor :draft_obj` here — it would shadow the
34
+ # base's lazy `draft_obj` reader (base.rb), which rebuilds the
35
+ # component from the `draft` attribute after from_hash. The plain
36
+ # accessor made every deserialized PSI draft render (and re-emit
37
+ # in to_hash) without its "/D<n>".
35
38
  end
36
39
  end
37
40
  end
@@ -73,7 +73,7 @@ module Pubid
73
73
  end
74
74
 
75
75
  # Complete IRE identifier - YEAR FIRST format
76
- rule(:ire_identifier) do
76
+ rule(:ire_identifier_year_first) do
77
77
  # Format: "52 IRE 7.S2" or "1952 IRE 7.S2"
78
78
  year.as(:year) >>
79
79
  space >>
@@ -84,6 +84,24 @@ module Pubid
84
84
  (space? >> (dash | str(",")).maybe >> space? >> year_full.as(:full_year)).maybe
85
85
  end
86
86
 
87
+ # Prefix-first format: "IRE 7.S2-1952" (pubid#316 family 2) — the
88
+ # year trails the designation instead of leading the identifier.
89
+ # The renderer always emits the year-first short-year form, so this
90
+ # spelling canonicalizes onto it ("IRE 7.S2-1952" → "52 IRE 7.S2").
91
+ rule(:ire_identifier_prefix_first) do
92
+ ire_prefix >>
93
+ space >>
94
+ (ire_type >> space).maybe >>
95
+ number >>
96
+ date
97
+ end
98
+
99
+ # Year-first is tried first so the original spellings keep their
100
+ # match; prefix-first claims only what year-first cannot.
101
+ rule(:ire_identifier) do
102
+ ire_identifier_year_first | ire_identifier_prefix_first
103
+ end
104
+
87
105
  root(:ire_identifier)
88
106
  end
89
107
  end