pubid 2.0.0.pre.alpha.9 → 2.0.0.pre.alpha.11

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 (432) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +33 -11
  3. data/conformance/pending.yaml +4 -0
  4. data/lib/pubid/adobe/identifier.rb +1 -3
  5. data/lib/pubid/adobe/parser.rb +10 -2
  6. data/lib/pubid/adobe.rb +1 -0
  7. data/lib/pubid/amca/builder.rb +2 -2
  8. data/lib/pubid/amca/identifiers/base.rb +49 -27
  9. data/lib/pubid/amca/identifiers/interpretation.rb +17 -9
  10. data/lib/pubid/amca/identifiers/publication.rb +13 -9
  11. data/lib/pubid/amca/identifiers/standard.rb +2 -0
  12. data/lib/pubid/amca/parser.rb +1 -1
  13. data/lib/pubid/amca/renderer.rb +4 -4
  14. data/lib/pubid/amca/urn_generator.rb +2 -2
  15. data/lib/pubid/amca.rb +2 -1
  16. data/lib/pubid/ansi/builder.rb +2 -2
  17. data/lib/pubid/ansi/identifier.rb +18 -0
  18. data/lib/pubid/ansi/parser.rb +1 -1
  19. data/lib/pubid/ansi/renderer.rb +2 -2
  20. data/lib/pubid/ansi.rb +5 -5
  21. data/lib/pubid/api/builder.rb +16 -2
  22. data/lib/pubid/api/identifier.rb +25 -4
  23. data/lib/pubid/api/identifiers/mpms.rb +10 -8
  24. data/lib/pubid/api/parser.rb +1 -1
  25. data/lib/pubid/api/renderer.rb +16 -7
  26. data/lib/pubid/api/single_identifier.rb +5 -12
  27. data/lib/pubid/api.rb +1 -0
  28. data/lib/pubid/ashrae/builder.rb +31 -10
  29. data/lib/pubid/ashrae/identifiers/addenda_package.rb +6 -0
  30. data/lib/pubid/ashrae/identifiers/addendum.rb +8 -0
  31. data/lib/pubid/ashrae/identifiers/base.rb +81 -5
  32. data/lib/pubid/ashrae/identifiers/combined_addenda.rb +7 -0
  33. data/lib/pubid/ashrae/identifiers/errata.rb +9 -0
  34. data/lib/pubid/ashrae/identifiers/guideline.rb +3 -0
  35. data/lib/pubid/ashrae/identifiers/interpretation.rb +16 -0
  36. data/lib/pubid/ashrae/identifiers/standard.rb +3 -0
  37. data/lib/pubid/ashrae/parser.rb +23 -8
  38. data/lib/pubid/ashrae/renderer.rb +6 -6
  39. data/lib/pubid/ashrae/supplement_identifier.rb +17 -2
  40. data/lib/pubid/ashrae/urn_generator.rb +8 -2
  41. data/lib/pubid/ashrae.rb +7 -1
  42. data/lib/pubid/asme/builder.rb +11 -2
  43. data/lib/pubid/asme/identifier.rb +9 -0
  44. data/lib/pubid/asme/identifiers/standard.rb +86 -2
  45. data/lib/pubid/asme/parser.rb +1 -1
  46. data/lib/pubid/asme/renderer.rb +3 -3
  47. data/lib/pubid/asme/single_identifier.rb +8 -1
  48. data/lib/pubid/asme/urn_generator.rb +2 -2
  49. data/lib/pubid/asme.rb +1 -0
  50. data/lib/pubid/astm/builder.rb +10 -3
  51. data/lib/pubid/astm/identifier.rb +9 -0
  52. data/lib/pubid/astm/identifiers/adjunct.rb +16 -1
  53. data/lib/pubid/astm/identifiers/code_number.rb +81 -0
  54. data/lib/pubid/astm/identifiers/data_series.rb +2 -0
  55. data/lib/pubid/astm/identifiers/iso_dual_published.rb +16 -0
  56. data/lib/pubid/astm/identifiers/manual.rb +10 -0
  57. data/lib/pubid/astm/identifiers/monograph.rb +2 -0
  58. data/lib/pubid/astm/identifiers/research_report.rb +10 -0
  59. data/lib/pubid/astm/identifiers/standard.rb +10 -0
  60. data/lib/pubid/astm/identifiers/technical_report.rb +2 -0
  61. data/lib/pubid/astm/identifiers/work_in_progress.rb +2 -0
  62. data/lib/pubid/astm/identifiers.rb +1 -0
  63. data/lib/pubid/astm/parser.rb +1 -1
  64. data/lib/pubid/astm/renderer.rb +1 -1
  65. data/lib/pubid/astm/single_identifier.rb +31 -1
  66. data/lib/pubid/astm/urn_generator.rb +7 -5
  67. data/lib/pubid/astm.rb +1 -0
  68. data/lib/pubid/bipm/builder.rb +66 -10
  69. data/lib/pubid/bipm/identifier.rb +134 -9
  70. data/lib/pubid/bipm/identifiers/committee_document.rb +12 -0
  71. data/lib/pubid/bipm/identifiers/guide.rb +11 -0
  72. data/lib/pubid/bipm/identifiers/meeting.rb +12 -0
  73. data/lib/pubid/bipm/identifiers/mep.rb +12 -0
  74. data/lib/pubid/bipm/identifiers/metrologia_article.rb +24 -0
  75. data/lib/pubid/bipm/identifiers/si_brochure.rb +13 -0
  76. data/lib/pubid/bipm/parser.rb +65 -12
  77. data/lib/pubid/bipm/renderer.rb +8 -0
  78. data/lib/pubid/bipm/urn_parser.rb +11 -3
  79. data/lib/pubid/bipm.rb +7 -1
  80. data/lib/pubid/bsi/builder.rb +48 -38
  81. data/lib/pubid/bsi/identifiers/adopted_european_norm.rb +36 -7
  82. data/lib/pubid/bsi/identifiers/adopted_international_standard.rb +7 -6
  83. data/lib/pubid/bsi/identifiers/british_industrial_practice.rb +0 -1
  84. data/lib/pubid/bsi/identifiers/bundled_identifier.rb +10 -0
  85. data/lib/pubid/bsi/identifiers/committee_document.rb +10 -1
  86. data/lib/pubid/bsi/identifiers/handbook.rb +0 -2
  87. data/lib/pubid/bsi/identifiers/practice_guide.rb +0 -1
  88. data/lib/pubid/bsi/identifiers/set.rb +11 -0
  89. data/lib/pubid/bsi/identifiers/standalone_amendment.rb +10 -1
  90. data/lib/pubid/bsi/parser.rb +1 -1
  91. data/lib/pubid/bsi/renderer.rb +63 -51
  92. data/lib/pubid/bsi/single_identifier.rb +16 -7
  93. data/lib/pubid/bsi/urn_generator.rb +11 -2
  94. data/lib/pubid/bsi.rb +7 -1
  95. data/lib/pubid/builder/base.rb +18 -8
  96. data/lib/pubid/bundled_identifier.rb +16 -6
  97. data/lib/pubid/calconnect/identifier.rb +9 -5
  98. data/lib/pubid/calconnect/parser.rb +1 -1
  99. data/lib/pubid/calconnect.rb +7 -1
  100. data/lib/pubid/ccsds/identifier.rb +9 -2
  101. data/lib/pubid/ccsds/identifiers/base.rb +4 -2
  102. data/lib/pubid/ccsds/identifiers/corrigendum.rb +2 -2
  103. data/lib/pubid/ccsds/parser.rb +1 -1
  104. data/lib/pubid/ccsds/single_identifier.rb +2 -2
  105. data/lib/pubid/ccsds.rb +1 -0
  106. data/lib/pubid/cen_cenelec/builder.rb +101 -76
  107. data/lib/pubid/cen_cenelec/identifier.rb +115 -3
  108. data/lib/pubid/cen_cenelec/identifiers/adopted_european_norm.rb +27 -16
  109. data/lib/pubid/cen_cenelec/identifiers/amendment.rb +46 -6
  110. data/lib/pubid/cen_cenelec/identifiers/base.rb +8 -9
  111. data/lib/pubid/cen_cenelec/identifiers/cen_report.rb +1 -1
  112. data/lib/pubid/cen_cenelec/identifiers/cen_workshop_agreement.rb +1 -1
  113. data/lib/pubid/cen_cenelec/identifiers/cenelec_harmonization_document.rb +1 -1
  114. data/lib/pubid/cen_cenelec/identifiers/consolidated_identifier.rb +29 -20
  115. data/lib/pubid/cen_cenelec/identifiers/corrigendum.rb +47 -8
  116. data/lib/pubid/cen_cenelec/identifiers/european_prestandard.rb +27 -2
  117. data/lib/pubid/cen_cenelec/identifiers/european_specification.rb +1 -1
  118. data/lib/pubid/cen_cenelec/identifiers/fragment.rb +15 -5
  119. data/lib/pubid/cen_cenelec/identifiers/guide.rb +1 -1
  120. data/lib/pubid/cen_cenelec/identifiers/harmonization_document.rb +1 -1
  121. data/lib/pubid/cen_cenelec/identifiers/technical_report.rb +1 -1
  122. data/lib/pubid/cen_cenelec/identifiers/technical_specification.rb +1 -1
  123. data/lib/pubid/cen_cenelec/parser.rb +1 -1
  124. data/lib/pubid/cen_cenelec/renderer.rb +55 -60
  125. data/lib/pubid/cen_cenelec/single_identifier.rb +20 -2
  126. data/lib/pubid/cen_cenelec/urn_generator.rb +66 -44
  127. data/lib/pubid/cen_cenelec.rb +28 -10
  128. data/lib/pubid/cie/builder.rb +10 -0
  129. data/lib/pubid/cie/identifier.rb +6 -1
  130. data/lib/pubid/cie/identifiers/bundle.rb +4 -2
  131. data/lib/pubid/cie/identifiers/conference.rb +2 -2
  132. data/lib/pubid/cie/identifiers/corrigendum.rb +2 -2
  133. data/lib/pubid/cie/identifiers/dual_published.rb +2 -2
  134. data/lib/pubid/cie/identifiers/identical.rb +2 -2
  135. data/lib/pubid/cie/identifiers/joint_published.rb +2 -2
  136. data/lib/pubid/cie/identifiers/proceedings.rb +8 -6
  137. data/lib/pubid/cie/identifiers/standard.rb +22 -13
  138. data/lib/pubid/cie/identifiers/supplement.rb +2 -2
  139. data/lib/pubid/cie/identifiers/tutorial_bundle.rb +2 -2
  140. data/lib/pubid/cie/parser.rb +1 -1
  141. data/lib/pubid/cie.rb +7 -1
  142. data/lib/pubid/conformance/checks.rb +72 -0
  143. data/lib/pubid/conformance/corpus/case.rb +55 -0
  144. data/lib/pubid/conformance/corpus.rb +36 -0
  145. data/lib/pubid/conformance/generator.rb +212 -0
  146. data/lib/pubid/conformance/pending.rb +63 -0
  147. data/lib/pubid/conformance/runner.rb +153 -0
  148. data/lib/pubid/conformance.rb +55 -0
  149. data/lib/pubid/core.rb +2 -0
  150. data/lib/pubid/csa/builder.rb +47 -47
  151. data/lib/pubid/csa/composite_identifier.rb +14 -19
  152. data/lib/pubid/csa/identifier.rb +143 -81
  153. data/lib/pubid/csa/identifiers/bundled.rb +23 -13
  154. data/lib/pubid/csa/identifiers/canadian_adopted.rb +13 -13
  155. data/lib/pubid/csa/identifiers/cec.rb +44 -4
  156. data/lib/pubid/csa/identifiers/combined.rb +95 -73
  157. data/lib/pubid/csa/identifiers/csa_adopted.rb +4 -4
  158. data/lib/pubid/csa/identifiers/package.rb +2 -2
  159. data/lib/pubid/csa/parser.rb +6 -2
  160. data/lib/pubid/csa/renderer.rb +13 -5
  161. data/lib/pubid/csa/single_identifier.rb +93 -4
  162. data/lib/pubid/csa/urn_generator.rb +9 -2
  163. data/lib/pubid/csa/wrapper_identifier.rb +39 -23
  164. data/lib/pubid/csa.rb +7 -1
  165. data/lib/pubid/doi/identifier.rb +6 -3
  166. data/lib/pubid/doi/parser.rb +1 -1
  167. data/lib/pubid/doi.rb +1 -0
  168. data/lib/pubid/easc/identifier.rb +0 -2
  169. data/lib/pubid/easc/parser.rb +10 -2
  170. data/lib/pubid/easc.rb +1 -0
  171. data/lib/pubid/ecma/builder.rb +23 -9
  172. data/lib/pubid/ecma/identifier.rb +81 -10
  173. data/lib/pubid/ecma/parser.rb +39 -4
  174. data/lib/pubid/ecma/renderer.rb +29 -8
  175. data/lib/pubid/ecma/urn_generator.rb +34 -13
  176. data/lib/pubid/ecma/urn_parser.rb +30 -14
  177. data/lib/pubid/ecma.rb +1 -0
  178. data/lib/pubid/errors.rb +80 -0
  179. data/lib/pubid/etsi/builder.rb +6 -1
  180. data/lib/pubid/etsi/identifiers/base.rb +34 -20
  181. data/lib/pubid/etsi/identifiers/etsi_standard.rb +120 -41
  182. data/lib/pubid/etsi/identifiers/supplement_identifier.rb +10 -0
  183. data/lib/pubid/etsi/parser.rb +1 -1
  184. data/lib/pubid/etsi.rb +1 -0
  185. data/lib/pubid/evs/builder.rb +26 -0
  186. data/lib/pubid/evs/identifier.rb +37 -0
  187. data/lib/pubid/evs/identifiers/national_adoption.rb +17 -0
  188. data/lib/pubid/evs/identifiers.rb +9 -0
  189. data/lib/pubid/evs/parser.rb +27 -0
  190. data/lib/pubid/evs/renderer.rb +30 -0
  191. data/lib/pubid/evs/urn_generator.rb +30 -0
  192. data/lib/pubid/evs/urn_parser.rb +80 -0
  193. data/lib/pubid/evs.rb +90 -0
  194. data/lib/pubid/export.rb +1 -0
  195. data/lib/pubid/format_detector.rb +1 -0
  196. data/lib/pubid/gb/identifier.rb +6 -3
  197. data/lib/pubid/gb/parser.rb +1 -1
  198. data/lib/pubid/gb.rb +1 -0
  199. data/lib/pubid/gost/builder.rb +46 -9
  200. data/lib/pubid/gost/identifier.rb +0 -2
  201. data/lib/pubid/gost/identifiers/foreign_reference.rb +1 -1
  202. data/lib/pubid/gost/parser.rb +10 -2
  203. data/lib/pubid/gost.rb +1 -0
  204. data/lib/pubid/iala/identifier.rb +9 -6
  205. data/lib/pubid/iala/parser.rb +10 -2
  206. data/lib/pubid/iala/urn_parser.rb +6 -2
  207. data/lib/pubid/iala.rb +1 -0
  208. data/lib/pubid/iana/builder.rb +3 -1
  209. data/lib/pubid/iana/identifier.rb +74 -6
  210. data/lib/pubid/iana/identifiers/registry.rb +36 -0
  211. data/lib/pubid/iana/parser.rb +1 -1
  212. data/lib/pubid/iana/renderer.rb +3 -0
  213. data/lib/pubid/iana/urn_generator.rb +5 -1
  214. data/lib/pubid/iana.rb +1 -0
  215. data/lib/pubid/identifier.rb +594 -10
  216. data/lib/pubid/identifier_metadata.rb +1 -0
  217. data/lib/pubid/idf/builder.rb +3 -3
  218. data/lib/pubid/idf/identifier.rb +18 -9
  219. data/lib/pubid/idf/parser.rb +1 -1
  220. data/lib/pubid/idf/renderer.rb +4 -4
  221. data/lib/pubid/idf/urn_generator.rb +1 -1
  222. data/lib/pubid/idf.rb +7 -1
  223. data/lib/pubid/iec/builder.rb +14 -7
  224. data/lib/pubid/iec/components.rb +0 -1
  225. data/lib/pubid/iec/identifier.rb +73 -83
  226. data/lib/pubid/iec/identifiers/amendment.rb +6 -2
  227. data/lib/pubid/iec/identifiers/component_specification.rb +0 -10
  228. data/lib/pubid/iec/identifiers/conformity_assessment.rb +0 -10
  229. data/lib/pubid/iec/identifiers/corrigendum.rb +2 -2
  230. data/lib/pubid/iec/identifiers/fragment_identifier.rb +2 -4
  231. data/lib/pubid/iec/identifiers/guide.rb +0 -16
  232. data/lib/pubid/iec/identifiers/international_standard.rb +15 -2
  233. data/lib/pubid/iec/identifiers/operational_document.rb +0 -10
  234. data/lib/pubid/iec/identifiers/publicly_available_specification.rb +0 -12
  235. data/lib/pubid/iec/identifiers/societal_technology_trend_report.rb +0 -10
  236. data/lib/pubid/iec/identifiers/systems_reference_document.rb +0 -10
  237. data/lib/pubid/iec/identifiers/technical_report.rb +0 -17
  238. data/lib/pubid/iec/identifiers/technical_specification.rb +0 -17
  239. data/lib/pubid/iec/identifiers/technology_report.rb +0 -10
  240. data/lib/pubid/iec/identifiers/test_report_form.rb +0 -11
  241. data/lib/pubid/iec/identifiers/white_paper.rb +0 -10
  242. data/lib/pubid/iec/identifiers/working_document.rb +24 -10
  243. data/lib/pubid/iec/parser.rb +2 -18
  244. data/lib/pubid/iec/renderer.rb +13 -10
  245. data/lib/pubid/iec/single_identifier.rb +33 -10
  246. data/lib/pubid/iec/urn_generator.rb +227 -61
  247. data/lib/pubid/iec/urn_parser.rb +42 -4
  248. data/lib/pubid/iec.rb +1 -0
  249. data/lib/pubid/ieee/aiee/identifier.rb +2 -2
  250. data/lib/pubid/ieee/aiee/parser.rb +1 -1
  251. data/lib/pubid/ieee/builder.rb +45 -1
  252. data/lib/pubid/ieee/identifiers/adopted_standard.rb +32 -0
  253. data/lib/pubid/ieee/identifiers/base.rb +42 -5
  254. data/lib/pubid/ieee/identifiers/csa_dual_published.rb +21 -0
  255. data/lib/pubid/ieee/identifiers/dual_published.rb +55 -0
  256. data/lib/pubid/ieee/identifiers/interpretation_identifier.rb +23 -0
  257. data/lib/pubid/ieee/identifiers/joint_development.rb +11 -7
  258. data/lib/pubid/ieee/identifiers/multi_numbered_identifier.rb +57 -5
  259. data/lib/pubid/ieee/identifiers/nesc/base.rb +2 -2
  260. data/lib/pubid/ieee/identifiers/nesc/draft.rb +2 -2
  261. data/lib/pubid/ieee/identifiers/nesc/handbook.rb +2 -2
  262. data/lib/pubid/ieee/identifiers/nesc/redline.rb +2 -2
  263. data/lib/pubid/ieee/identifiers/nesc/standard.rb +2 -2
  264. data/lib/pubid/ieee/ire/identifier.rb +2 -2
  265. data/lib/pubid/ieee/ire/parser.rb +1 -1
  266. data/lib/pubid/ieee/nesc/parser.rb +1 -1
  267. data/lib/pubid/ieee/parser.rb +1 -1
  268. data/lib/pubid/ieee/renderer.rb +5 -1
  269. data/lib/pubid/ieee/urn_generator.rb +32 -3
  270. data/lib/pubid/ieee.rb +7 -1
  271. data/lib/pubid/ieee_debug.rb +1 -0
  272. data/lib/pubid/ietf/builder.rb +24 -8
  273. data/lib/pubid/ietf/identifiers/base.rb +25 -28
  274. data/lib/pubid/ietf/identifiers/bcp.rb +11 -0
  275. data/lib/pubid/ietf/identifiers/fyi.rb +11 -0
  276. data/lib/pubid/ietf/identifiers/internet_draft.rb +18 -2
  277. data/lib/pubid/ietf/identifiers/rfc.rb +2 -0
  278. data/lib/pubid/ietf/identifiers/serialization.rb +51 -0
  279. data/lib/pubid/ietf/identifiers/std.rb +11 -0
  280. data/lib/pubid/ietf/identifiers.rb +1 -0
  281. data/lib/pubid/ietf/parser.rb +27 -9
  282. data/lib/pubid/ietf/renderer.rb +44 -6
  283. data/lib/pubid/ietf/urn_generator.rb +20 -6
  284. data/lib/pubid/ietf/urn_parser.rb +10 -2
  285. data/lib/pubid/ietf.rb +12 -2
  286. data/lib/pubid/iho/identifiers/base.rb +10 -3
  287. data/lib/pubid/iho/parser.rb +1 -1
  288. data/lib/pubid/iho.rb +1 -0
  289. data/lib/pubid/isbn/identifier.rb +25 -7
  290. data/lib/pubid/isbn/parser.rb +1 -1
  291. data/lib/pubid/isbn.rb +1 -0
  292. data/lib/pubid/iso/bundled_identifier.rb +5 -3
  293. data/lib/pubid/iso/combined_identifier.rb +5 -3
  294. data/lib/pubid/iso/identifier.rb +12 -7
  295. data/lib/pubid/iso/identifiers/directives_supplement.rb +4 -4
  296. data/lib/pubid/iso/parser.rb +1 -1
  297. data/lib/pubid/iso.rb +7 -1
  298. data/lib/pubid/itu/identifiers/addendum.rb +2 -2
  299. data/lib/pubid/itu/identifiers/amendment.rb +2 -2
  300. data/lib/pubid/itu/identifiers/base.rb +19 -5
  301. data/lib/pubid/itu/identifiers/corrigendum.rb +2 -2
  302. data/lib/pubid/itu/identifiers/errata.rb +2 -2
  303. data/lib/pubid/itu/identifiers/supplement.rb +2 -2
  304. data/lib/pubid/itu/parser.rb +1 -1
  305. data/lib/pubid/itu.rb +1 -0
  306. data/lib/pubid/jcgm/builder.rb +1 -1
  307. data/lib/pubid/jcgm/identifier.rb +10 -0
  308. data/lib/pubid/jcgm/identifiers/meeting.rb +1 -1
  309. data/lib/pubid/jcgm/parser.rb +10 -3
  310. data/lib/pubid/jcgm/renderer.rb +10 -3
  311. data/lib/pubid/jcgm/single_identifier.rb +24 -15
  312. data/lib/pubid/jcgm/urn_generator.rb +1 -1
  313. data/lib/pubid/jcgm/urn_parser.rb +156 -14
  314. data/lib/pubid/jcgm.rb +10 -0
  315. data/lib/pubid/jis/identifier.rb +10 -3
  316. data/lib/pubid/jis/parser.rb +1 -1
  317. data/lib/pubid/jis.rb +1 -0
  318. data/lib/pubid/nist/components/stage.rb +2 -2
  319. data/lib/pubid/nist/configuration.rb +6 -8
  320. data/lib/pubid/nist/identifiers/base.rb +12 -2
  321. data/lib/pubid/nist/identifiers/circular.rb +9 -2
  322. data/lib/pubid/nist/identifiers/circular_supplement.rb +14 -5
  323. data/lib/pubid/nist/identifiers/commercial_standards_monthly.rb +7 -1
  324. data/lib/pubid/nist/identifiers/crpl_report.rb +7 -1
  325. data/lib/pubid/nist/identifiers/handbook.rb +9 -2
  326. data/lib/pubid/nist/identifiers/internal_report.rb +12 -5
  327. data/lib/pubid/nist/identifiers/miscellaneous_publication.rb +14 -6
  328. data/lib/pubid/nist/identifiers/monograph.rb +14 -7
  329. data/lib/pubid/nist/identifiers/report.rb +14 -6
  330. data/lib/pubid/nist/parser.rb +1 -1
  331. data/lib/pubid/nist/supplement_identifier.rb +16 -1
  332. data/lib/pubid/nist.rb +7 -1
  333. data/lib/pubid/oasis/builder.rb +12 -10
  334. data/lib/pubid/oasis/identifier.rb +89 -11
  335. data/lib/pubid/oasis/parser.rb +1 -1
  336. data/lib/pubid/oasis.rb +7 -1
  337. data/lib/pubid/ogc/identifier.rb +9 -5
  338. data/lib/pubid/ogc/parser.rb +1 -1
  339. data/lib/pubid/ogc.rb +1 -0
  340. data/lib/pubid/oiml/builder.rb +5 -1
  341. data/lib/pubid/oiml/identifiers/basic_publication.rb +2 -0
  342. data/lib/pubid/oiml/identifiers/bulletin.rb +36 -0
  343. data/lib/pubid/oiml/identifiers/code_number.rb +90 -0
  344. data/lib/pubid/oiml/identifiers/document.rb +2 -0
  345. data/lib/pubid/oiml/identifiers/expert_report.rb +2 -0
  346. data/lib/pubid/oiml/identifiers/guide.rb +2 -0
  347. data/lib/pubid/oiml/identifiers/recommendation.rb +2 -0
  348. data/lib/pubid/oiml/identifiers/seminar_report.rb +2 -0
  349. data/lib/pubid/oiml/identifiers/vocabulary.rb +2 -0
  350. data/lib/pubid/oiml/identifiers.rb +1 -0
  351. data/lib/pubid/oiml/parser.rb +1 -1
  352. data/lib/pubid/oiml/single_identifier.rb +26 -42
  353. data/lib/pubid/oiml/supplement_identifier.rb +27 -0
  354. data/lib/pubid/oiml.rb +7 -1
  355. data/lib/pubid/omg/builder.rb +1 -0
  356. data/lib/pubid/omg/identifier.rb +20 -3
  357. data/lib/pubid/omg/parser.rb +64 -11
  358. data/lib/pubid/omg/renderer.rb +13 -1
  359. data/lib/pubid/omg.rb +2 -1
  360. data/lib/pubid/parser/grammar.rb +74 -0
  361. data/lib/pubid/parser.rb +2 -0
  362. data/lib/pubid/parsers/mr_string.rb +19 -0
  363. data/lib/pubid/plateau/parser.rb +1 -1
  364. data/lib/pubid/plateau.rb +10 -0
  365. data/lib/pubid/prefixes_support.rb +1 -0
  366. data/lib/pubid/renderers/annotator.rb +233 -0
  367. data/lib/pubid/renderers/base.rb +13 -0
  368. data/lib/pubid/renderers/directives_renderer.rb +3 -3
  369. data/lib/pubid/renderers/human_readable.rb +3 -3
  370. data/lib/pubid/renderers/mr_string.rb +15 -9
  371. data/lib/pubid/renderers.rb +1 -0
  372. data/lib/pubid/rendering/numbering.rb +25 -7
  373. data/lib/pubid/rendering.rb +1 -0
  374. data/lib/pubid/sae/identifiers/base.rb +9 -2
  375. data/lib/pubid/sae/parser.rb +1 -1
  376. data/lib/pubid/sae.rb +1 -0
  377. data/lib/pubid/schema/declaration.rb +33 -0
  378. data/lib/pubid/schema/error.rb +14 -0
  379. data/lib/pubid/schema/identifier_type.rb +23 -0
  380. data/lib/pubid/schema/loader.rb +117 -0
  381. data/lib/pubid/schema/typed_stage.rb +24 -0
  382. data/lib/pubid/schema.rb +25 -0
  383. data/lib/pubid/tgpp/builder.rb +1 -1
  384. data/lib/pubid/tgpp/identifier.rb +21 -5
  385. data/lib/pubid/tgpp/identifiers/technical_report.rb +2 -2
  386. data/lib/pubid/tgpp/identifiers/technical_specification.rb +2 -2
  387. data/lib/pubid/tgpp/parser.rb +22 -6
  388. data/lib/pubid/tgpp/renderer.rb +21 -5
  389. data/lib/pubid/tgpp/urn_generator.rb +27 -8
  390. data/lib/pubid/tgpp/urn_parser.rb +8 -6
  391. data/lib/pubid/tgpp.rb +7 -1
  392. data/lib/pubid/type_resolver.rb +16 -1
  393. data/lib/pubid/un/identifier.rb +6 -3
  394. data/lib/pubid/un/parser.rb +1 -1
  395. data/lib/pubid/un.rb +1 -0
  396. data/lib/pubid/urn_parser/errors.rb +8 -1
  397. data/lib/pubid/urn_parser.rb +1 -0
  398. data/lib/pubid/utils.rb +1 -0
  399. data/lib/pubid/version.rb +1 -1
  400. data/lib/pubid/w3c/builder.rb +5 -5
  401. data/lib/pubid/w3c/identifier.rb +38 -9
  402. data/lib/pubid/w3c/parser.rb +1 -1
  403. data/lib/pubid/w3c/renderer.rb +1 -1
  404. data/lib/pubid/w3c/urn_generator.rb +2 -2
  405. data/lib/pubid/w3c.rb +1 -0
  406. data/lib/pubid/xsf/identifier.rb +9 -5
  407. data/lib/pubid/xsf/parser.rb +17 -4
  408. data/lib/pubid/xsf.rb +7 -1
  409. data/lib/pubid.rb +399 -21
  410. data/lib/tasks/conformance.rake +34 -0
  411. data/lib/tasks/docs.rake +3 -3
  412. data/lib/tasks/schema.rake +88 -0
  413. data/schema/core/joint_prefixes.yaml +24 -0
  414. data/schema/iec.yaml +1017 -0
  415. data/schema/iso.yaml +1586 -0
  416. data/schema/schema.schema.yaml +96 -0
  417. metadata +38 -17
  418. data/archived-gems/pubid-ccsds/update_codes.yaml +0 -1
  419. data/archived-gems/pubid-iec/stages.yaml +0 -129
  420. data/archived-gems/pubid-iec/update_codes.yaml +0 -67
  421. data/archived-gems/pubid-ieee/update_codes.yaml +0 -104
  422. data/archived-gems/pubid-iso/stages.yaml +0 -106
  423. data/archived-gems/pubid-iso/update_codes.yaml +0 -4
  424. data/archived-gems/pubid-itu/i18n.yaml +0 -13
  425. data/archived-gems/pubid-itu/series.yaml +0 -42
  426. data/archived-gems/pubid-nist/publishers.yaml +0 -6
  427. data/archived-gems/pubid-nist/series.yaml +0 -121
  428. data/archived-gems/pubid-nist/update_codes.yaml +0 -93
  429. data/archived-gems/pubid-plateau/update_codes.yaml +0 -6
  430. data/lib/pubid/cen_cenelec/supplement_identifier.rb +0 -48
  431. data/lib/pubid/iec/components/code.rb +0 -36
  432. /data/{archived-gems/pubid-nist → data/nist}/stages.yaml +0 -0
@@ -4,31 +4,173 @@ module Pubid
4
4
  module Jcgm
5
5
  # Parses JCGM URNs back into identifiers.
6
6
  #
7
- # UrnGenerator emits `urn:jcgm:{number}:{year}` for guides and
8
- # `urn:jcgm:meeting:{number}:{year}` for committee/meeting records.
7
+ # Identifiers are rebuilt **directly from the URN segments** rather than
8
+ # rendered to a human-readable string and re-parsed (the BIPM UrnParser
9
+ # pattern). Round-tripping through the grammar flattened the segments into
10
+ # one string and silently lost everything the string form could not carry:
11
+ # a GUM guide (`urn:jcgm:gum.6:2020`) raised, and the language and
12
+ # supplement segments were dropped, so a corrigendum read back as the
13
+ # standard it amends — a different document, with no error.
9
14
  #
10
- # Examples:
11
- # - urn:jcgm:200:2012 → JCGM 200:2012
12
- # - urn:jcgm:100:2008 → JCGM 100:2008
13
- # - urn:jcgm:meeting:17:2012 → JCGM 17th Meeting (2012)
15
+ # Shapes emitted by UrnGenerator, and read back here:
16
+ # urn:jcgm:200:2008 → JCGM 200:2008
17
+ # urn:jcgm:100:2008:en → JCGM 100:2008(E)
18
+ # urn:jcgm:200:2012:en,fr → JCGM 200:2012(E/F)
19
+ # urn:jcgm:GUM → JCGM GUM
20
+ # urn:jcgm:gum.6:2020 → JCGM GUM-6:2020
21
+ # urn:jcgm:200:2008:corrigendum → JCGM 200:2008 Corrigendum
22
+ # urn:jcgm:101:2008:corrigendum:1:2009 → JCGM 101:2008/Cor 1:2009
23
+ # urn:jcgm:100:2008:amendment:1:2023 → JCGM 100:2008/Amd 1:2023
24
+ # urn:jcgm:meeting:17:2012 → JCGM 17th Meeting (2012)
25
+ # urn:jcgm:meeting:11 → JCGM 11st Meeting
26
+ #
27
+ # KNOWN GAP, on the generator side and unchanged here: a full date is
28
+ # rendered as its year alone (`JCGM GUM-1:2022-11-28` →
29
+ # `urn:jcgm:gum.1:2022`), so a URN cannot restore the month and day.
30
+ # Widening it would change already-published URNs.
14
31
  class UrnParser < Pubid::UrnParser::Base
32
+ MEETING_MARKER = "meeting"
33
+
34
+ # The GUM-guide number is written "gum.<n>" by UrnGenerator.
35
+ GUM_PREFIX = "gum."
36
+
37
+ # The supplement marker UrnGenerator writes is the typed stage's
38
+ # `type_code`; the abbreviation is what `Jcgm.locate_stage` matches on.
39
+ SUPPLEMENT_ABBRS = {
40
+ "corrigendum" => "Cor",
41
+ "amendment" => "Amd",
42
+ }.freeze
43
+
44
+ MEETING_ABBR = "Meeting"
45
+
46
+ # Mirrors `year_digits` in the shared parser rules.
47
+ YEAR_PATTERN = /\A(?:19|20)\d{2}\z/
48
+
49
+ # A language segment is one or more comma-joined ISO codes.
50
+ LANGUAGES_PATTERN = /\A[a-z]{2}(?:,[a-z]{2})*\z/
51
+
52
+ # Language codes back to the single-letter form the identifier prints;
53
+ # the builder maps the other way when parsing "(E)" / "(E/F)".
54
+ LANGUAGE_LETTERS = Pubid::Builder::Base::LANG_CHAR_MAP.invert.freeze
55
+
15
56
  def parse_urn(urn)
16
57
  parts = split_parts(strip_namespace(urn))
17
- return parse_meeting_urn(parts) if parts.first == "meeting"
58
+ return parse_meeting_urn(parts) if parts.first == MEETING_MARKER
18
59
 
19
- number, year = parts
20
- text = "JCGM #{number}"
21
- text += ":#{year}" if year
22
- flavor_parse(text)
60
+ parse_document_urn(parts)
23
61
  end
24
62
 
25
63
  private
26
64
 
27
- # urn:jcgm:meeting:<number>:<year> -> "JCGM <ordinal> Meeting (<year>)"
65
+ # urn:jcgm:meeting:<number>[:<year>] -> Identifiers::Meeting
66
+ #
67
+ # The reconstruction must agree with a parsed identifier attribute for
68
+ # attribute, so `typed_stage` comes from the same registry lookup
69
+ # `Builder#locate_typed_stage` uses. (The attribute default now agrees
70
+ # too — it no longer sets `original_abbr` — so this mirrors the builder
71
+ # for clarity rather than out of necessity.)
28
72
  def parse_meeting_urn(parts)
29
73
  _, number, year = parts
30
- ordinal = Identifiers::Meeting.ordinal(number)
31
- flavor_parse("JCGM #{ordinal} Meeting (#{year})")
74
+
75
+ Identifiers::Meeting.new(
76
+ number: meeting_number(number),
77
+ date: urn_date(year),
78
+ typed_stage: Jcgm.locate_stage(MEETING_ABBR),
79
+ )
80
+ end
81
+
82
+ # A guide, a GUM guide, or a supplement wrapping one of those. The
83
+ # supplement marker splits the segments: everything before it describes
84
+ # the base document, everything after is the supplement's own number and
85
+ # date.
86
+ def parse_document_urn(parts)
87
+ marker_at = parts.index { |segment| SUPPLEMENT_ABBRS.key?(segment) }
88
+ return build_document(parts) unless marker_at
89
+
90
+ build_supplement(
91
+ parts[marker_at],
92
+ build_document(parts[0...marker_at]),
93
+ parts[(marker_at + 1)..] || [],
94
+ )
95
+ end
96
+
97
+ # The class comes from the same registry the builder uses, so a URN and a
98
+ # parsed reference resolve to one class and one typed_stage.
99
+ def build_supplement(marker, base, tail)
100
+ typed_stage = Jcgm.locate_stage(SUPPLEMENT_ABBRS.fetch(marker))
101
+ number, year = tail
102
+
103
+ Jcgm.locate_type(typed_stage.type_code).new(
104
+ base: base,
105
+ number: (number.nil? || number.empty? ? nil : number.to_s),
106
+ date: urn_date(year),
107
+ typed_stage: typed_stage,
108
+ )
109
+ end
110
+
111
+ # <number>[:<year>][:<languages>] — the number segment carries the
112
+ # "gum." prefix for a GUM guide, which is what selects the class.
113
+ #
114
+ # Guide and GumGuide deliberately take their `typed_stage` from the
115
+ # attribute default: the grammar emits no type token for them, so
116
+ # `Builder#build` fills it from `published_typed_stage` too, and both
117
+ # paths agree.
118
+ def build_document(parts)
119
+ number, *rest = parts
120
+ require_number!(number)
121
+ gum = number.start_with?(GUM_PREFIX)
122
+
123
+ (gum ? Identifiers::GumGuide : Identifiers::Guide).new(
124
+ number: (gum ? number.delete_prefix(GUM_PREFIX) : number).to_s,
125
+ date: urn_date(segment_matching(rest, YEAR_PATTERN)),
126
+ languages: urn_languages(segment_matching(rest, LANGUAGES_PATTERN)),
127
+ )
128
+ end
129
+
130
+ def require_number!(segment)
131
+ return unless segment.nil? || segment.empty?
132
+
133
+ raise Pubid::UrnParser::Errors::ParseError,
134
+ "JCGM URN has no document number"
135
+ end
136
+
137
+ def segment_matching(segments, pattern)
138
+ segments.find { |segment| segment.match?(pattern) }
139
+ end
140
+
141
+ # The ordinal, normalized the way `Identifiers::Meeting.ordinal` does it
142
+ # (`number.to_i`), so "011" reads back as "11" and a missing or
143
+ # non-numeric segment as "0" — the behaviour of the previous
144
+ # render-and-re-parse implementation.
145
+ def meeting_number(segment)
146
+ segment.to_i.to_s
147
+ end
148
+
149
+ # nil for an absent year; a Date for a well-formed one. The grammar
150
+ # accepts only a 19xx/20xx year, so anything else is a malformed URN and
151
+ # must still be rejected now that no re-parse validates it.
152
+ def urn_date(segment)
153
+ return nil if segment.nil? || segment.empty?
154
+
155
+ unless segment.match?(YEAR_PATTERN)
156
+ raise Pubid::UrnParser::Errors::ParseError,
157
+ "Invalid year in JCGM URN: #{segment.inspect}"
158
+ end
159
+
160
+ Pubid::Components::Date.new(year: segment)
161
+ end
162
+
163
+ # "en" -> (E), "en,fr" -> (E/F). `original_code` is what the renderer
164
+ # prints, so it is restored from the code rather than left nil.
165
+ def urn_languages(segment)
166
+ return nil if segment.nil? || segment.empty?
167
+
168
+ segment.split(",").map do |code|
169
+ Pubid::Components::Language.new(
170
+ code: code,
171
+ original_code: LANGUAGE_LETTERS.fetch(code, code),
172
+ )
173
+ end
32
174
  end
33
175
  end
34
176
  end
data/lib/pubid/jcgm.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Jcgm
5
6
  extend Pubid::PrefixesSupport
@@ -22,6 +23,15 @@ module Pubid
22
23
  # @param identifier [String] the identifier string to parse
23
24
  # @return [Identifier] the parsed identifier
24
25
  def self.parse(identifier)
26
+ unless identifier.is_a?(String)
27
+ raise Pubid::Errors::InvalidInputError,
28
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
29
+ end
30
+
31
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
32
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
33
+ end
34
+
25
35
  parser = Parser.new
26
36
  builder = Builder.new
27
37
 
@@ -175,12 +175,19 @@ module Pubid
175
175
  # Parse a JIS identifier string into an identifier object
176
176
  # @param identifier [String] The JIS identifier string to parse
177
177
  # @return [Identifier] The appropriate identifier object
178
- # @raise [RuntimeError] If parsing fails
178
+ # @raise [Pubid::Errors::ParseError] If parsing fails
179
179
  def self.parse(identifier)
180
+ unless identifier.is_a?(String)
181
+ raise Pubid::Errors::InvalidInputError,
182
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
183
+ end
184
+
185
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
186
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
187
+ end
188
+
180
189
  parsed = Parser.parse(identifier)
181
190
  Builder.build(parsed)
182
- rescue Parslet::ParseFailed => e
183
- raise "Failed to parse JIS identifier '#{identifier}': #{e.message}"
184
191
  end
185
192
  end
186
193
  end
@@ -4,7 +4,7 @@ require "parslet"
4
4
 
5
5
  module Pubid
6
6
  module Jis
7
- class Parser < Parslet::Parser
7
+ class Parser < ::Pubid::Parser::Grammar
8
8
  # Japanese character normalization
9
9
  rule(:jp_dash) { str("ー") } # Full-width dash
10
10
  rule(:jp_space) { str(" ") } # Full-width space
data/lib/pubid/jis.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Jis
5
6
  extend Pubid::PrefixesSupport
@@ -16,10 +16,10 @@ module Pubid
16
16
  attribute :id, :string # i, f, 1-9
17
17
  attribute :type, :string # pd, wd, prd
18
18
 
19
- # Load stages from V1 YAML config
19
+ # Load stages from data/nist/stages.yaml
20
20
  STAGES = YAML.load_file(
21
21
  File.join(File.dirname(__FILE__),
22
- "../../../../archived-gems/pubid-nist/stages.yaml"),
22
+ "../../../../data/nist/stages.yaml"),
23
23
  ).freeze
24
24
 
25
25
  # Render stage in specified format
@@ -58,13 +58,7 @@ module Pubid
58
58
  private
59
59
 
60
60
  def default_series_path
61
- # Prefer data/nist/series.yaml (the canonical home in the monorepo);
62
- # fall back to archived-gems/pubid-nist/series.yaml for backward
63
- # compat with the v1 layout.
64
- monorepo = File.join(__dir__, "../../../data/nist/series.yaml")
65
- return monorepo if File.exist?(monorepo)
66
-
67
- File.join(__dir__, "../../../archived-gems/pubid-nist/series.yaml")
61
+ File.join(__dir__, "../../../data/nist/series.yaml")
68
62
  end
69
63
 
70
64
  def load_series
@@ -78,6 +72,10 @@ module Pubid
78
72
  end
79
73
  end
80
74
 
81
- class ConfigurationError < StandardError; end
75
+ # Raised when the NIST series YAML cannot be loaded. Included in the
76
+ # marker module so `rescue Pubid::Errors::Error` stays exhaustive.
77
+ class ConfigurationError < StandardError
78
+ include ::Pubid::Errors::Error
79
+ end
82
80
  end
83
81
  end
@@ -170,6 +170,10 @@ module Pubid
170
170
  end
171
171
 
172
172
  def initialize(**attributes)
173
+ # This override calls `super()` with no attributes and assigns through
174
+ # setters instead, so it bypasses the base constructor's coercion and
175
+ # unknown-key check. Apply them here to keep the cross-flavor contract.
176
+ attributes = self.class.normalize_init_attributes(attributes)
173
177
  super()
174
178
 
175
179
  attrs = self.class.attributes
@@ -304,13 +308,19 @@ module Pubid
304
308
  # @param format [:full, :long, :abbreviated, :short, :mr] output format
305
309
  def to_s(format = nil)
306
310
  # Handle both keyword argument (hash) and positional argument (symbol/string)
311
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
307
312
  format = format[:format] if format.is_a?(Hash)
308
313
 
309
314
  # Default to parsed_format if available (preserves input format on round-trip)
310
315
  # Falls back to :short format for output (normalization)
311
316
  # Explicit format parameter always overrides parsed_format
312
- render(format: :human,
313
- nist_format: format || parsed_format&.to_sym || :short)
317
+ rendered = render(format: :human,
318
+ nist_format: format || parsed_format&.to_sym || :short)
319
+
320
+ # NIST's `to_s` takes a POSITIONAL format and its `render` override
321
+ # builds no rendering context, so neither the flag nor the context
322
+ # reaches the shared hook. Apply the annotation fallback here.
323
+ annotate_plain_render(rendered, annotated: annotated)
314
324
  end
315
325
 
316
326
  # NIST has its own defined MR format (to_mr_style) used by relaton-nist
@@ -53,7 +53,14 @@ module Pubid
53
53
  # Override to_s for CIRC-specific edition+year rendering
54
54
  # CIRC uses dot notation: "NBS CIRC 11e2.1915" instead of "NBS CIRC 11e2-1915"
55
55
  def to_s(format = nil)
56
- result = super
56
+ # The rewrite below is `$`-anchored, so it has to run on the PLAIN
57
+ # string: with the flag set, `super` returns a string ending in
58
+ # "</span>" and the anchor no longer matches. Render plain, rewrite,
59
+ # then annotate.
60
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
61
+ format = format[:format] if format.is_a?(Hash)
62
+
63
+ result = super(format)
57
64
 
58
65
  # For CIRC edition patterns with additional_text (year), render with dot notation
59
66
  # "11e2-1915" format: edition.id="2", additional_text="1915" → render as "11e2.1915"
@@ -65,7 +72,7 @@ module Pubid
65
72
  "e#{edition.id || '?'}.#{edition.additional_text}")
66
73
  end
67
74
 
68
- result
75
+ annotate_plain_render(result, annotated: annotated)
69
76
  end
70
77
  end
71
78
  end
@@ -37,13 +37,22 @@ module Pubid
37
37
  attribute :implicit_supplement, :boolean # true for implicit supplements (e.g., "145r11/1925")
38
38
 
39
39
  def to_s(format = :short)
40
+ # A caller passing `annotated:` reaches this positional parameter as a
41
+ # Hash. Normalise it away before `super`, so the annotation is applied
42
+ # once, here, and covers the date-range branch too — that branch
43
+ # returns a hand-composed string and never reached `super` at all.
44
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
45
+ format = format[:format] || :short if format.is_a?(Hash)
46
+
40
47
  # Handle date range supplements (no base identifier)
41
- if supplement_date_range_start && supplement_date_range_end
42
- return "NBS CIRC sup#{supplement_date_range_start}-#{supplement_date_range_end}"
43
- end
48
+ result = if supplement_date_range_start && supplement_date_range_end
49
+ "NBS CIRC sup#{supplement_date_range_start}-#{supplement_date_range_end}"
50
+ else
51
+ # Use parent's rendering for base + supplement
52
+ super(format)
53
+ end
44
54
 
45
- # Use parent's rendering for base + supplement
46
- super
55
+ annotate_plain_render(result, annotated: annotated)
47
56
  end
48
57
  end
49
58
  end
@@ -41,6 +41,12 @@ module Pubid
41
41
  end
42
42
 
43
43
  def to_s(format = nil)
44
+ # A caller passing `annotated:` reaches this positional parameter as
45
+ # a Hash — Ruby converts keywords for a method that accepts none.
46
+ # The same idiom `Identifiers::Base#to_s` uses. This renderer
47
+ # ignores `format`, so only the flag is read.
48
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
49
+
44
50
  result = "#{publisher} #{series}"
45
51
 
46
52
  # Proper Volume and Part components
@@ -55,7 +61,7 @@ module Pubid
55
61
  result += " #{vol_str}n#{issue_number.number}"
56
62
  end
57
63
 
58
- result
64
+ annotate_plain_render(result, annotated: annotated)
59
65
  end
60
66
  end
61
67
  end
@@ -88,6 +88,12 @@ module Pubid
88
88
  end
89
89
 
90
90
  def to_s(format = nil)
91
+ # A caller passing `annotated:` reaches this positional parameter as
92
+ # a Hash — Ruby converts keywords for a method that accepts none.
93
+ # The same idiom `Identifiers::Base#to_s` uses. This renderer
94
+ # ignores `format`, so only the flag is read.
95
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
96
+
91
97
  # Use actual series attribute if it contains subseries (e.g., "CRPL-F-B")
92
98
  # Otherwise use default series_code ("CRPL")
93
99
  series_to_render = if series&.value&.include?("CRPL-F-")
@@ -124,7 +130,7 @@ module Pubid
124
130
  result += append_short_components
125
131
 
126
132
  result += range_notation if range_notation
127
- result
133
+ annotate_plain_render(result, annotated: annotated)
128
134
  end
129
135
  end
130
136
  end
@@ -34,8 +34,15 @@ module Pubid
34
34
  # Override to_s to handle edition+year pattern with dash instead of dot
35
35
  # Pattern: "44e2-1955" (dash separator) not "44e2.1955" (dot separator)
36
36
  def to_s(format = nil)
37
+ # The rewrite below is `$`-anchored, so it has to run on the PLAIN
38
+ # string: with the flag set, `super` returns a string ending in
39
+ # "</span>" and the anchor no longer matches. Render plain, rewrite,
40
+ # then annotate.
41
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
42
+ format = format[:format] if format.is_a?(Hash)
43
+
37
44
  # Use the base to_s method first
38
- result = super
45
+ result = super(format)
39
46
 
40
47
  # If edition has additional_text (year), replace dot with dash
41
48
  if edition&.additional_text && !edition.additional_text.empty?
@@ -43,7 +50,7 @@ module Pubid
43
50
  "#{edition.type}#{edition.id}-#{edition.additional_text}")
44
51
  end
45
52
 
46
- result
53
+ annotate_plain_render(result, annotated: annotated)
47
54
  end
48
55
  end
49
56
  end
@@ -37,6 +37,11 @@ module Pubid
37
37
  # @param format [Symbol, Hash, nil] output format
38
38
  # @return [String] formatted identifier
39
39
  def to_s(format = nil)
40
+ # `super` is handed a SYMBOL below, so the base's annotation hook
41
+ # never sees the flag. Capture it here and wrap what comes back —
42
+ # exactly once, because the base returned plain text.
43
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
44
+
40
45
  # Handle both keyword argument (hash) and positional argument (symbol/string)
41
46
  effective_format = if format.is_a?(Hash)
42
47
  format[:format]
@@ -45,11 +50,13 @@ module Pubid
45
50
  end
46
51
 
47
52
  # If explicit format is specified, use it. Otherwise, default to short.
48
- if effective_format.nil?
49
- super(:short)
50
- else
51
- super(effective_format)
52
- end
53
+ rendered = if effective_format.nil?
54
+ super(:short)
55
+ else
56
+ super(effective_format)
57
+ end
58
+
59
+ annotate_plain_render(rendered, annotated: annotated)
53
60
  end
54
61
  end
55
62
  end
@@ -36,13 +36,21 @@ module Pubid
36
36
  "MP"
37
37
  end
38
38
 
39
+ # A caller passing `annotated:` reaches this positional parameter as a
40
+ # Hash — Ruby converts keywords for a method that accepts none. The same
41
+ # idiom `Identifiers::Base#to_s` uses.
39
42
  def to_s(format = :short)
40
- case format
41
- when :mr
42
- to_mr_style
43
- else
44
- to_short_style
45
- end
43
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
44
+ format = format[:format] || :short if format.is_a?(Hash)
45
+
46
+ rendered = case format
47
+ when :mr
48
+ to_mr_style
49
+ else
50
+ to_short_style
51
+ end
52
+
53
+ annotate_plain_render(rendered, annotated: annotated)
46
54
  end
47
55
 
48
56
  public
@@ -35,6 +35,11 @@ module Pubid
35
35
  end
36
36
 
37
37
  def to_s(format = nil)
38
+ # `super` is handed a SYMBOL below, so the base's annotation hook
39
+ # never sees the flag. Capture it here and wrap what comes back —
40
+ # exactly once, because the base returned plain text.
41
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
42
+
38
43
  # Handle both keyword argument (hash) and positional argument (symbol/string)
39
44
  effective_format = if format.is_a?(Hash)
40
45
  format[:format]
@@ -43,13 +48,15 @@ module Pubid
43
48
  end
44
49
 
45
50
  # If explicit format is specified, use it. Otherwise, default to short.
46
- if effective_format.nil?
47
- super(:short)
48
- elsif effective_format == :mr
49
- to_mr_style
50
- else
51
- super(effective_format)
52
- end
51
+ rendered = if effective_format.nil?
52
+ super(:short)
53
+ elsif effective_format == :mr
54
+ to_mr_style
55
+ else
56
+ super(effective_format)
57
+ end
58
+
59
+ annotate_plain_render(rendered, annotated: annotated)
53
60
  end
54
61
 
55
62
  private
@@ -39,13 +39,21 @@ module Pubid
39
39
  "RPT"
40
40
  end
41
41
 
42
+ # A caller passing `annotated:` reaches this positional parameter as a
43
+ # Hash — Ruby converts keywords for a method that accepts none. The same
44
+ # idiom `Identifiers::Base#to_s` uses.
42
45
  def to_s(format = :short)
43
- case format
44
- when :mr
45
- to_mr_style
46
- else
47
- to_short_style
48
- end
46
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
47
+ format = format[:format] || :short if format.is_a?(Hash)
48
+
49
+ rendered = case format
50
+ when :mr
51
+ to_mr_style
52
+ else
53
+ to_short_style
54
+ end
55
+
56
+ annotate_plain_render(rendered, annotated: annotated)
49
57
  end
50
58
 
51
59
  private
@@ -6,7 +6,7 @@ module Pubid
6
6
  module Nist
7
7
  # Parser class for NIST identifiers
8
8
  # Single Responsibility: Parsing NIST identifier syntax
9
- class Parser < Parslet::Parser
9
+ class Parser < ::Pubid::Parser::Grammar
10
10
 
11
11
  # Class-level parse method with preprocessing.
12
12
  # Delegates all string normalization to Nist::Preprocessor, then
@@ -25,13 +25,28 @@ module Pubid
25
25
  base&.series
26
26
  end
27
27
 
28
+ # Four exits — a date range, a base-less delegation, an update form and
29
+ # the canonical one — so the composition stays whole in its own method
30
+ # and `to_s` wraps its single result. `super` is not reachable from a
31
+ # private method, so it is handed in as a block.
28
32
  def to_s(format = :short)
33
+ annotated = format.is_a?(Hash) ? format[:annotated] : nil
34
+ format = format[:format] || :short if format.is_a?(Hash)
35
+
36
+ rendered = render_plain(format) { super(format) }
37
+
38
+ annotate_plain_render(rendered, annotated: annotated)
39
+ end
40
+
41
+ private
42
+
43
+ def render_plain(format)
29
44
  # Handle date range supplements (no base identifier)
30
45
  if supplement_date_range_start && supplement_date_range_end
31
46
  return "NBS CIRC sup#{supplement_date_range_start}-#{supplement_date_range_end}"
32
47
  end
33
48
 
34
- return super unless base
49
+ return yield unless base
35
50
 
36
51
  result = base.to_s(format)
37
52
 
data/lib/pubid/nist.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "pubid"
3
4
  module Pubid
4
5
  module Nist
5
6
  extend Pubid::PrefixesSupport
@@ -67,8 +68,13 @@ module Pubid
67
68
  # @param identifier [String] the identifier string to parse
68
69
  # @return [Identifier] the parsed identifier
69
70
  def self.parse(identifier)
71
+ unless identifier.is_a?(String)
72
+ raise Pubid::Errors::InvalidInputError,
73
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
74
+ end
75
+
70
76
  if identifier.length > Pubid::MAX_INPUT_LENGTH
71
- raise ArgumentError, Pubid::INPUT_TOO_LONG_MESSAGE
77
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
72
78
  end
73
79
 
74
80
  # Use the Parser class's preprocessing method