pubid 2.0.20 → 2.0.22

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +73 -31
  3. data/data/iec/legacy-notation.md +298 -0
  4. data/data/iho/s4-reserved-specs.yaml +15 -0
  5. data/data/iso/historic-schemes.md +368 -0
  6. data/data/parg/bipm.json +1 -1
  7. data/data/parg/bipm.parg +46 -11
  8. data/data/parg/iec.json +1 -1
  9. data/data/parg/iho.json +1 -1
  10. data/data/parg/iho.parg +128 -11
  11. data/data/parg/iso.json +1 -1
  12. data/data/parg/iso.parg +142 -2
  13. data/data/parg/plateau.json +1 -1
  14. data/data/parg/tables/iec_typed_stages.yaml +3 -0
  15. data/data/parg/tables/typed_stages_ballots.yaml +6 -0
  16. data/data/parg/tables/typed_stages_supplements.yaml +1 -0
  17. data/lib/pubid/bipm/CLAUDE.md +6 -1
  18. data/lib/pubid/bipm/builder.rb +20 -1
  19. data/lib/pubid/bipm/renderer.rb +14 -2
  20. data/lib/pubid/bipm/urn_generator.rb +80 -14
  21. data/lib/pubid/bipm/urn_parser.rb +70 -6
  22. data/lib/pubid/ecma/CLAUDE.md +12 -0
  23. data/lib/pubid/ecma/identifier.rb +1 -2
  24. data/lib/pubid/ecma/parser.rb +49 -0
  25. data/lib/pubid/iec/CLAUDE.md +9 -1
  26. data/lib/pubid/iec/builder.rb +47 -0
  27. data/lib/pubid/iec/identifier.rb +19 -70
  28. data/lib/pubid/iec/identifiers/amendment.rb +34 -0
  29. data/lib/pubid/iec/identifiers/corrigendum.rb +6 -1
  30. data/lib/pubid/iec/identifiers/fragment_identifier.rb +3 -3
  31. data/lib/pubid/iec/single_identifier.rb +69 -4
  32. data/lib/pubid/iec/supplement_identifier.rb +89 -83
  33. data/lib/pubid/iho/CLAUDE.md +54 -1
  34. data/lib/pubid/iho/builder.rb +105 -2
  35. data/lib/pubid/iho/identifiers/base.rb +52 -1
  36. data/lib/pubid/iho/pubid_urn_schema.rb +12 -4
  37. data/lib/pubid/iho/renderer.rb +39 -2
  38. data/lib/pubid/iho/reserved_specs.rb +54 -0
  39. data/lib/pubid/iho/urn_generator.rb +6 -0
  40. data/lib/pubid/iho/urn_parser.rb +50 -9
  41. data/lib/pubid/iho.rb +1 -0
  42. data/lib/pubid/iso/CLAUDE.md +49 -0
  43. data/lib/pubid/iso/builder.rb +77 -0
  44. data/lib/pubid/iso/identifiers/amendment.rb +1 -1
  45. data/lib/pubid/iso/single_identifier.rb +7 -1
  46. data/lib/pubid/nist/CLAUDE.md +49 -0
  47. data/lib/pubid/nist/builder.rb +5 -0
  48. data/lib/pubid/nist/identifiers/base.rb +42 -1
  49. data/lib/pubid/nist/identifiers/federal_information_processing_standards.rb +44 -5
  50. data/lib/pubid/nist/preprocessor.rb +18 -0
  51. data/lib/pubid/nist/urn_parser.rb +1 -1
  52. data/lib/pubid/plateau/CLAUDE.md +35 -0
  53. data/lib/pubid/plateau/builder.rb +4 -0
  54. data/lib/pubid/plateau/identifiers/handbook.rb +10 -2
  55. data/lib/pubid/plateau/pubid_urn_schema.rb +1 -0
  56. data/lib/pubid/plateau/renderer.rb +6 -1
  57. data/lib/pubid/sae/CLAUDE.md +11 -2
  58. data/lib/pubid/version.rb +1 -1
  59. metadata +7 -2
data/data/parg/iso.parg CHANGED
@@ -11,6 +11,20 @@
11
11
  # below, whose captures carry the idf_ prefix so they never collide
12
12
  # with the ISO ones and can bind as scalars.
13
13
  #
14
+ # Historic TYPE-first ballot faces (decode-to-modern, owner ruling
15
+ # 2026-11-12): the DAM-family token in TYPE position — "ISO/IEC FDAM 1
16
+ # 14496-12:2004", "ISO/PDAM 19973-2" — and the space-attached ballot
17
+ # face "ISO 15877-5:2009 FDAM 1". Both branches sit LAST in the
18
+ # identifier alternation, so they are purely additive: every face an
19
+ # earlier branch parses keeps its exact parse. The ballot token
20
+ # captures as ballot_type_with_stage; a number on the TYPE token is
21
+ # space-bounded (ballot_number) so "PDAM 19973-2" keeps 19973 in the
22
+ # base; an on-base stamp's number/date capture as ballot_stamp_* (the
23
+ # stamp token itself matches uncaptured — the ballot's stage comes
24
+ # from the TYPE token and the decode keeps no spelling). The builder
25
+ # folds the captures into ONE amendment layer over the whole base, the
26
+ # number taken from the TYPE token, the stamp, or the implied 1.
27
+ #
14
28
  # Edition 2 with typed model bindings: every capture the builder
15
29
  # consumes is bound. Supplement layers capture into position-scoped
16
30
  # names (supp_* on the first supplement, supp2_* on the second,
@@ -23,7 +37,7 @@
23
37
 
24
38
  use idf
25
39
 
26
- grammar PubidIso version "0.4.0" edition 2 {
40
+ grammar PubidIso version "0.5.0" edition 2 {
27
41
 
28
42
  # ---- lexical (pubid/parser/common_parse_rules.rb + ISO override) ----
29
43
  digit = %x30-39
@@ -47,6 +61,7 @@ grammar PubidIso version "0.4.0" edition 2 {
47
61
  # inline the alt so the capture holds one scalar span) ----
48
62
  typed_stage_abbrev = alt from_table "typed_stages" column "abbr"
49
63
  supp_stage_abbrev = alt from_table "typed_stages_supplements" column "abbr"
64
+ ballot_stage_abbrev = alt from_table "typed_stages_ballots" column "abbr"
50
65
  directives_stage_abbrev = alt from_table "directives_typed_stages" column "abbr"
51
66
  directives_supp_stage_abbrev = alt from_table "directives_supplements_typed_stages" column "abbr"
52
67
  idf_stage_abbrev = alt from_table "idf_typed_stages" column "abbr"
@@ -395,6 +410,76 @@ grammar PubidIso version "0.4.0" edition 2 {
395
410
  )
396
411
  }
397
412
 
413
+ # ---- historic TYPE-first ballot faces (decode-to-modern) ----
414
+ ## The DAM-family ballot token in TYPE position. Captured under its
415
+ ## own name: the supplement tables' captures stay reserved for the
416
+ ## on-base layers, and the ballot's stage is the one the decode keeps.
417
+ ballot_type_with_stage = {
418
+ (
419
+ (alt from_table "typed_stages_ballots" column "abbr")
420
+ ) as ballot_type_with_stage
421
+ }
422
+ ## An on-base stamp riding a ballot face ("/Amd 9:2001", "/Amd.1",
423
+ ## "/Am1:2006"): it carries the amendment's number and ballot date
424
+ ## while its own token letters are decode-away (matched uncaptured —
425
+ ## the ballot's stage comes from the TYPE token). Mirrors the three
426
+ ## supplement tails: a (glued or spaced) number with an optional
427
+ ## date, or a dot number.
428
+ ballot_stamp = {
429
+ slash supp_stage_abbrev
430
+ (
431
+ (
432
+ [space]
433
+ ((1*5digit)) as ballot_stamp_number
434
+ [
435
+ [space]
436
+ (
437
+ (colon / dash) [
438
+ (("19" / "20") 2*digit !digit [(dash 2*digit)] [(dash 2*digit)]) as ballot_stamp_date
439
+ / (("--" / "—")) as ballot_stamp_undated_marker
440
+ ]
441
+ )
442
+ ]
443
+ )
444
+ / ( dot (1*digit) as ballot_stamp_dot_number )
445
+ )
446
+ }
447
+ ## "ISO/IEC FDAM 1 14496-12:2004", "ISO/PDAM 19973-2": the ballot
448
+ ## token first, then the base kept whole. The TYPE-token number is
449
+ ## space-bounded on both sides — mandatory trailing space — so
450
+ ## "PDAM 19973-2" cannot read 19973 as the ballot number: with no
451
+ ## second space the group backtracks and 19973 parses in second_part.
452
+ ballot_supplement_identifier = {
453
+ prefix_with_copublishers
454
+ [space]
455
+ [slash]
456
+ ballot_type_with_stage
457
+ [ space ((1*digit)) as ballot_number space ]
458
+ [space]
459
+ [type_with_stage]
460
+ [space]
461
+ second_part
462
+ [ ballot_stamp ]
463
+ }
464
+ ## The space-attached ballot face: "ISO 15877-5:2009 FDAM 1" — the
465
+ ## base designation first, then the ballot token attached by a space.
466
+ ## The base front is inlined (not supplement_base_no_third): that
467
+ ## rule ends in base_third_part_edition, whose own optional space
468
+ ## would consume the separator the ballot token needs. No base
469
+ ## edition is served here — none of the stored or owner-cited ballot
470
+ ## faces carries one.
471
+ ballot_space_supplement_identifier = {
472
+ [ (("Guide" / "GUIDE") as type_with_stage_fr) space ]
473
+ [prefix_with_copublishers]
474
+ [space]
475
+ [slash]
476
+ [type_with_stage]
477
+ [space]
478
+ second_part
479
+ space ballot_type_with_stage
480
+ [ space ((1*digit)) as ballot_number ]
481
+ }
482
+
398
483
  # ---- directives: "ISO/IEC DIR 1", "ISO/IEC JTC 1 DIR", supplements,
399
484
  # bundled "ISO/IEC DIR 1:2022 + IEC SUP:2022" ----
400
485
  directives_publisher_subgroup = ("JTC 1") as subgroup
@@ -609,7 +694,8 @@ grammar PubidIso version "0.4.0" edition 2 {
609
694
  }
610
695
 
611
696
  ## The ISO identifier root: common-text twin form first, then the
612
- ## single spine.
697
+ ## single spine. The historic ballot faces go last — purely additive
698
+ ## branches (an input an earlier member parses keeps that parse).
613
699
  identifier = {
614
700
  bare_tc_document
615
701
  / nsb_iso_identifier
@@ -623,6 +709,8 @@ grammar PubidIso version "0.4.0" edition 2 {
623
709
  / supplement_identifier
624
710
  / joint_identifier
625
711
  / identifier_copublishers
712
+ / ballot_space_supplement_identifier
713
+ / ballot_supplement_identifier
626
714
  }
627
715
 
628
716
  entry identifier: identifier
@@ -712,6 +800,12 @@ bs3_undated_marker -> bs3_undated_marker (string)
712
800
  idf_supp_stage_iteration2 -> idf_supp_stage_iteration2 (string)
713
801
  idf_supp_date2 -> idf_supp_date2 (string)
714
802
  idf_supp_undated_marker -> idf_supp_undated_marker (string)
803
+ ballot_type_with_stage -> ballot_type_with_stage (string)
804
+ ballot_number -> ballot_number (string)
805
+ ballot_stamp_number -> ballot_stamp_number (string)
806
+ ballot_stamp_dot_number -> ballot_stamp_dot_number (string)
807
+ ballot_stamp_date -> ballot_stamp_date (string)
808
+ ballot_stamp_undated_marker -> ballot_stamp_undated_marker (string)
715
809
  }
716
810
 
717
811
  ## In-file grammar tests: run at compile time, embedded in the artifact,
@@ -728,9 +822,55 @@ bs3_undated_marker -> bs3_undated_marker (string)
728
822
  accept "ISO 5537|IDF 26"
729
823
  accept "ISO 4055/Amd 1:2021"
730
824
  accept "ISO/R 9:1962"
825
+ accept "ISO/IEC FDAM 1 14496-12:2004"
826
+ accept "ISO/PDAM 19973-2"
827
+ accept "ISO/IEC FPDAM 9075/1"
828
+ accept "ISO/IEC FPDAM 10373-6/Amd 9:2001"
829
+ accept "ISO/PDAM 19110:2005/Amd.1"
830
+ accept "ISO/IEC PDAM TR 9573-11/Amd.1"
831
+ accept "ISO/IEC PDAM 9541-1/Amd.4"
832
+ accept "ISO/IEC PDAM 2 10646:2012"
833
+ accept "ISO 15877-5:2009 FDAM 1"
834
+ accept "ISO 19776-2:2005/Am1:2006"
731
835
  reject "nonsense"
836
+ reject "ISO/PDAM"
837
+ reject "ISO/FDAM"
732
838
  example "ISO/IEC WD TS 25025" { publisher: "ISO" }
733
839
  example "JCGM 200:2008(F)" { publisher: "JCGM" }
840
+ example "ISO/IEC FDAM 1 14496-12:2004" {
841
+ ballot_type_with_stage: "FDAM"
842
+ ballot_number: "1"
843
+ number_with_part: "14496-12"
844
+ date: "2004"
845
+ }
846
+ example "ISO/PDAM 19973-2" {
847
+ ballot_type_with_stage: "PDAM"
848
+ number_with_part: "19973-2"
849
+ }
850
+ example "ISO/IEC FPDAM 10373-6/Amd 9:2001" {
851
+ ballot_type_with_stage: "FPDAM"
852
+ ballot_stamp_number: "9"
853
+ ballot_stamp_date: "2001"
854
+ }
855
+ example "ISO/PDAM 19110:2005/Amd.1" {
856
+ ballot_type_with_stage: "PDAM"
857
+ ballot_stamp_number: "1"
858
+ }
859
+ example "ISO/IEC FPDAM 9075/1" {
860
+ ballot_type_with_stage: "FPDAM"
861
+ number_with_part: "9075/1"
862
+ }
863
+ example "ISO 15877-5:2009 FDAM 1" {
864
+ ballot_type_with_stage: "FDAM"
865
+ ballot_number: "1"
866
+ number_with_part: "15877-5"
867
+ date: "2009"
868
+ }
869
+ example "ISO/IEC FDAM 19776-2:2005/Am1:2006" {
870
+ ballot_type_with_stage: "FDAM"
871
+ ballot_stamp_number: "1"
872
+ ballot_stamp_date: "2006"
873
+ }
734
874
  }
735
875
  }
736
876
 
@@ -1 +1 @@
1
- {"bindings":null,"version":"0.1.0","grammar":"PubidPlateau","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Entity":{"atom":69}},{"Entity":{"atom":49}},{"Str":{"pattern":"PLATEAU"}},{"Str":{"pattern":" "}},{"Entity":{"atom":8}},{"Str":{"pattern":"Handbook"}},{"Str":{"pattern":"Technical Report"}},{"Alternative":{"atoms":[5,6]}},{"Named":{"name":"type","atom":7}},{"Str":{"pattern":" "}},{"Entity":{"atom":15}},{"Str":{"pattern":"#"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":12,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"number","atom":13}},{"Sequence":{"atoms":[11,14]}},{"Entity":{"atom":23}},{"Str":{"pattern":"-"}},{"Str":{"pattern":"_"}},{"Alternative":{"atoms":[17,18]}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":20,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"annex","atom":21}},{"Sequence":{"atoms":[19,22]}},{"Repetition":{"atom":16,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":47}},{"Str":{"pattern":" "}},{"Entity":{"atom":37}},{"Str":{"pattern":"第"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":29,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":32,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[30,31,33]}},{"Named":{"name":"edition","atom":34}},{"Str":{"pattern":"版"}},{"Sequence":{"atoms":[28,35,36]}},{"Entity":{"atom":45}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":39,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":42,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[40,41,43]}},{"Named":{"name":"edition","atom":44}},{"Alternative":{"atoms":[27,38]}},{"Sequence":{"atoms":[26,46]}},{"Repetition":{"atom":25,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[2,3,4,9,10,24,48]}},{"Entity":{"atom":58}},{"Str":{"pattern":"PLATEAU"}},{"Str":{"pattern":" "}},{"Entity":{"atom":8}},{"Str":{"pattern":" "}},{"Entity":{"atom":15}},{"Entity":{"atom":23}},{"Repetition":{"atom":56,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[51,52,53,54,55,57]}},{"Alternative":{"atoms":[1,50]}},{"Named":{"name":"base","atom":59}},{"Entity":{"atom":68}},{"Str":{"pattern":" "}},{"Str":{"pattern":"Annex"}},{"Str":{"pattern":" "}},{"Entity":{"atom":67}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Named":{"name":"annex_letter","atom":66}},{"Sequence":{"atoms":[62,63,64,65]}},{"Sequence":{"atoms":[60,61]}},{"Entity":{"atom":49}},{"Entity":{"atom":58}},{"Alternative":{"atoms":[0,70,71]}}],"root":72},"bindings":[{"capture":"base","path":"base","type":"raw","card":null,"preprocess":null},{"capture":"type","path":"type","type":"string","card":null,"preprocess":null},{"capture":"number","path":"number","type":"string","card":null,"preprocess":null},{"capture":"annex","path":"annex","type":"string","card":null,"preprocess":null},{"capture":"edition","path":"edition","type":"string","card":null,"preprocess":null},{"capture":"annex_letter","path":"annex_letter","type":"string","card":null,"preprocess":null}]}},"preprocess":{},"tables":{},"lint":{"order_warnings":["rule identifier: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule identifier: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule identifier: branches 2 and 3 are order-dependent (shared first bytes; ordered choice is decisive)"]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"PLATEAU Handbook #3","expect":{}},{"entry":"identifier","kind":"accept","input":"PLATEAU Technical Report #1","expect":{}},{"entry":"identifier","kind":"reject","input":"nonsense","expect":{}}],"docs":{},"source":"# Port of lib/pubid/plateau/parser.rb: PLATEAU Handbook/Technical Report\n# #NN[-annex][ edition], plus Annex supplements. Japanese canonical\n# edition (第X.Y版) and legacy Latin X.Y both capture the same atom.\n#\n# Edition 2: every reference inside a captured body is inlined to raw\n# terms (a rule reference materializes as a named node in the edition-2\n# tree, so only a raw capture binds a scalar span). The base wrapper\n# keeps the v1 tree shape and the bindings name the scalar leaves.\n\ngrammar PubidPlateau version \"0.1.0\" edition 2 {\n doc_type = (\"Handbook\" / \"Technical Report\") as type\n number = \"#\" (1*%x30-39) as number\n annex = (\"-\" / \"_\") (1*%x30-39) as annex\n edition_part = \"第\" (1*%x30-39 \".\" 1*%x30-39) as edition \"版\"\n edition_latin = (1*%x30-39 \".\" 1*%x30-39) as edition\n edition = \" \" (edition_part / edition_latin)\n annex_letter = (%x41-5A) as annex_letter\n annex_supplement = \" \" \"Annex\" \" \" annex_letter\n handbook = \"PLATEAU\" \" \" doc_type \" \" number [annex] [edition]\n technical_report = \"PLATEAU\" \" \" doc_type \" \" number [annex]\n annex_identifier = (handbook / technical_report) as base annex_supplement\n identifier = annex_identifier / handbook / technical_report\n\n entry identifier: identifier\n\n test {\n accept \"PLATEAU Handbook #3\"\n accept \"PLATEAU Technical Report #1\"\n reject \"nonsense\"\n }\n\n bindings identifier {\n # The builder dispatches on base && annex_letter and RE-parses the base\n # subtree recursively (build_annex calls build(parsed[:base])), so base\n # binds uncast — the nested {type, number, annex, edition} hash.\n base -> base (raw)\n type -> type (string)\n number -> number (string)\n annex -> annex (string)\n edition -> edition (string)\n annex_letter -> annex_letter (string)\n }\n}\n","source_hash":"c794ad3672c17b5cacdfa398ef912bc11fa61559a5dec42e38e83f85ea5a03ae","unicode_tables":"onigmo (Ruby 3.4.8)","compiler_version":"1.3.114","edition":2,"checksum":"sha256:18c528e9b4808183ec56567581aedff00d4e275644fe861b9db4a2dd2dbac3e8"}
1
+ {"bindings":null,"version":"0.1.0","grammar":"PubidPlateau","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Entity":{"atom":87}},{"Entity":{"atom":67}},{"Str":{"pattern":"PLATEAU"}},{"Str":{"pattern":" "}},{"Entity":{"atom":8}},{"Str":{"pattern":"Handbook"}},{"Str":{"pattern":"Technical Report"}},{"Alternative":{"atoms":[5,6]}},{"Named":{"name":"type","atom":7}},{"Str":{"pattern":" "}},{"Entity":{"atom":15}},{"Str":{"pattern":"#"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":12,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"number","atom":13}},{"Sequence":{"atoms":[11,14]}},{"Entity":{"atom":23}},{"Str":{"pattern":"-"}},{"Str":{"pattern":"_"}},{"Alternative":{"atoms":[17,18]}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":20,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"annex","atom":21}},{"Sequence":{"atoms":[19,22]}},{"Repetition":{"atom":16,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":47}},{"Str":{"pattern":" "}},{"Entity":{"atom":37}},{"Str":{"pattern":"第"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":29,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":32,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[30,31,33]}},{"Named":{"name":"edition","atom":34}},{"Str":{"pattern":"版"}},{"Sequence":{"atoms":[28,35,36]}},{"Entity":{"atom":45}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":39,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":42,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[40,41,43]}},{"Named":{"name":"edition","atom":44}},{"Alternative":{"atoms":[27,38]}},{"Sequence":{"atoms":[26,46]}},{"Entity":{"atom":63}},{"Entity":{"atom":52}},{"Str":{"pattern":" "}},{"Re":{"pattern":"[\\u3000]"}},{"Alternative":{"atoms":[50,51]}},{"Named":{"name":"volume_sep","atom":49}},{"Str":{"pattern":"本編"}},{"Str":{"pattern":"+"}},{"Str":{"pattern":"+"}},{"Alternative":{"atoms":[55,56]}},{"Str":{"pattern":"付録"}},{"Sequence":{"atoms":[57,58]}},{"Repetition":{"atom":59,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[54,60]}},{"Named":{"name":"volume","atom":61}},{"Sequence":{"atoms":[53,62]}},{"Repetition":{"atom":48,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[25,64]}},{"Repetition":{"atom":65,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[2,3,4,9,10,24,66]}},{"Entity":{"atom":76}},{"Str":{"pattern":"PLATEAU"}},{"Str":{"pattern":" "}},{"Entity":{"atom":8}},{"Str":{"pattern":" "}},{"Entity":{"atom":15}},{"Entity":{"atom":23}},{"Repetition":{"atom":74,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[69,70,71,72,73,75]}},{"Alternative":{"atoms":[1,68]}},{"Named":{"name":"base","atom":77}},{"Entity":{"atom":86}},{"Str":{"pattern":" "}},{"Str":{"pattern":"Annex"}},{"Str":{"pattern":" "}},{"Entity":{"atom":85}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Named":{"name":"annex_letter","atom":84}},{"Sequence":{"atoms":[80,81,82,83]}},{"Sequence":{"atoms":[78,79]}},{"Entity":{"atom":67}},{"Entity":{"atom":76}},{"Alternative":{"atoms":[0,88,89]}}],"root":90},"bindings":[{"capture":"base","path":"base","type":"raw","card":null,"preprocess":null},{"capture":"type","path":"type","type":"string","card":null,"preprocess":null},{"capture":"number","path":"number","type":"string","card":null,"preprocess":null},{"capture":"annex","path":"annex","type":"string","card":null,"preprocess":null},{"capture":"edition","path":"edition","type":"string","card":null,"preprocess":null},{"capture":"volume","path":"volume","type":"string","card":null,"preprocess":null},{"capture":"annex_letter","path":"annex_letter","type":"string","card":null,"preprocess":null}]}},"preprocess":{},"tables":{},"lint":{"order_warnings":["rule identifier: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule identifier: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule identifier: branches 2 and 3 are order-dependent (shared first bytes; ordered choice is decisive)"]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"PLATEAU Handbook #3","expect":{}},{"entry":"identifier","kind":"accept","input":"PLATEAU Technical Report #1","expect":{}},{"entry":"identifier","kind":"accept","input":"PLATEAU Handbook #00 第5.0版 本編","expect":{}},{"entry":"identifier","kind":"accept","input":"PLATEAU Handbook #00 第5.0版 本編+付録","expect":{}},{"entry":"identifier","kind":"accept","input":"PLATEAU Handbook #00 第5.0版 本編+付録","expect":{}},{"entry":"identifier","kind":"reject","input":"nonsense","expect":{}}],"docs":{},"source":"# Port of lib/pubid/plateau/parser.rb: PLATEAU Handbook/Technical Report\n# #NN[-annex][ edition], plus Annex supplements. Japanese canonical\n# edition (第X.Y版) and legacy Latin X.Y both capture the same atom.\n#\n# Edition 2: every reference inside a captured body is inlined to raw\n# terms (a rule reference materializes as a named node in the edition-2\n# tree, so only a raw capture binds a scalar span). The base wrapper\n# keeps the v1 tree shape and the bindings name the scalar leaves.\n\ngrammar PubidPlateau version \"0.1.0\" edition 2 {\n doc_type = (\"Handbook\" / \"Technical Report\") as type\n number = \"#\" (1*%x30-39) as number\n annex = (\"-\" / \"_\") (1*%x30-39) as annex\n edition_part = \"第\" (1*%x30-39 \".\" 1*%x30-39) as edition \"版\"\n edition_latin = (1*%x30-39 \".\" 1*%x30-39) as edition\n edition = \" \" (edition_part / edition_latin)\n # MLIT's volume label after the edition: 本編 (main volume) or 本編+付録\n # (main volume with appendix). ASCII or full-width (U+3000) space; ASCII\n # \"+\" or full-width \"+\" (U+FF0B), which the builder normalizes to \"+\".\n volume_sep = \" \" / %x3000\n volume = volume_sep (\"本編\" [(\"+\" / \"+\") \"付録\"]) as volume\n annex_letter = (%x41-5A) as annex_letter\n annex_supplement = \" \" \"Annex\" \" \" annex_letter\n handbook = \"PLATEAU\" \" \" doc_type \" \" number [annex] [edition [volume]]\n technical_report = \"PLATEAU\" \" \" doc_type \" \" number [annex]\n annex_identifier = (handbook / technical_report) as base annex_supplement\n identifier = annex_identifier / handbook / technical_report\n\n entry identifier: identifier\n\n test {\n accept \"PLATEAU Handbook #3\"\n accept \"PLATEAU Technical Report #1\"\n accept \"PLATEAU Handbook #00 第5.0版 本編\"\n accept \"PLATEAU Handbook #00 第5.0版 本編+付録\"\n accept \"PLATEAU Handbook #00 第5.0版 本編+付録\"\n reject \"nonsense\"\n }\n\n bindings identifier {\n # The builder dispatches on base && annex_letter and RE-parses the base\n # subtree recursively (build_annex calls build(parsed[:base])), so base\n # binds uncast — the nested {type, number, annex, edition} hash.\n base -> base (raw)\n type -> type (string)\n number -> number (string)\n annex -> annex (string)\n edition -> edition (string)\n volume -> volume (string)\n annex_letter -> annex_letter (string)\n }\n}\n","source_hash":"f5a43fb4d638687462490f2b05b635501ac21375c917b10e28f2cad554985476","unicode_tables":"onigmo (Ruby 3.4.10)","compiler_version":"1.3.114","edition":2,"checksum":"sha256:e876e3871cd842ded4df7a9ad906a428fbf11ab466631f489f555dd9508bef8a"}
@@ -4,8 +4,10 @@
4
4
  - abbr: "ANW Amd"
5
5
  - abbr: "WD Amd"
6
6
  - abbr: "CDAM"
7
+ - abbr: "PDAM"
7
8
  - abbr: "DAM"
8
9
  - abbr: "FDAM"
10
+ - abbr: "FPDAM"
9
11
  - abbr: "PRF Amd"
10
12
  - abbr: "Amd"
11
13
  - abbr: "AMD"
@@ -14,6 +16,7 @@
14
16
  - abbr: "ANW Cor"
15
17
  - abbr: "WDCor"
16
18
  - abbr: "CDCor"
19
+ - abbr: "CDCOR"
17
20
  - abbr: "DCOR"
18
21
  - abbr: "FDCOR"
19
22
  - abbr: "PRF Cor"
@@ -0,0 +1,6 @@
1
+ - abbr: "PDAM"
2
+ - abbr: "DAM"
3
+ - abbr: "DAmd"
4
+ - abbr: "FDAM"
5
+ - abbr: "FDAmd"
6
+ - abbr: "FPDAM"
@@ -44,6 +44,7 @@
44
44
  - abbr: "Amd"
45
45
  - abbr: "AMD"
46
46
  - abbr: "Amd."
47
+ - abbr: "Am"
47
48
  - abbr: "PWI Cor"
48
49
  - abbr: "NP Cor"
49
50
  - abbr: "AWI Cor"
@@ -12,7 +12,12 @@ These notes were part of the root `CLAUDE.md`. Read them before you change `lib/
12
12
 
13
13
  - **Routing reaches BIPM (`Pubid.parse`, pubid#644)**: the note above says "`Pubid.parse` auto-routing does not resolve **any** BIPM form today … so `PREFIXES` was left alone" — that is no longer true, in two halves. **(1) `PREFIXES` gained the two SI-family series tokens** `SI Brochure` and `SI MEP`: both lead real printed references on their own (`SI Brochure 9e v3.01 (2019/2024, E)` — the spelling relaton's `Bibliography#search` strips the `BIPM ` off, per the note above — and `SI MEP 10`), and before this both parsed **directly** but the router could not route them (no prefix owner ⇒ no non-exact fallback). "Rapport" stays excluded per the ambiguous-token policy (generic French word). **(2) The quoted-title form** `BIPM "The International System of Units"` — the brochure IS titled that — is mapped onto the parseable `BIPM SI Brochure` section form by **`data/bipm/update_codes.yaml`** (the CIPM-MRA precedent), so it parses as a **partial reference** that wildcards every edition, exactly like `SI Brochure Part 1`; the bare `"The International System of Units"` maps to `SI Brochure`. Both are normalizing parses and live with the loose-forms expectations, never in the byte-exact fixtures. The **French title is deliberately not mapped**: it names the F-language record specifically, and mapping it would silently drop typed language information (the loose-forms rule above). Also note the router's prefix-owner mechanism is what makes the normalizing parses routable: `Pubid::Bipm.parse("BIPM SI Brochure")` happens to round-trip exactly, but `SI Brochure 9e …` renders with the leading `BIPM ` the input lacked — without prefix-owner status the router would reject it. (hand-off: router-printed-forms-644.)
14
14
 
15
- - **Corpus-cited forms + guide printings (pubid#657)**: the metanorma-bipm fetch queue cites two spellings the grammar rejected, and the 2026-11 guide update adds printings of its own. All are **parse-in forms over the existing canonical identities** — the canonical render follows the guide, nothing that already parsed changed. **(1) The em-dash meeting variant** `CGPM -- Meeting 1 (1889)`: `meeting_en` gained `<group> -- Meeting <n>` and `<group> -- <n><ord> Meeting` branches (the corpus cites only the first; the second is the same reordering over the other word order). They build through the existing `Meeting` path, so the canonical render is `CGPM 1st Meeting (1889)`. **(2) The publisher-prefixed decision** `BIPM DECN CIPM/101-1 (2012, EN)` (and `, FR`): new `committee_prefixed` rule — a **literal `BIPM`** lead (the constant publisher, deliberately NOT the `group` table, which contains BIPM and would collide with the second `group` capture), `type_abbrev`, then the body-qualified id `{group}/{number}`. Builds through `build_committee` short-form, so it equals `CIPM DECN 101-1 (2012, E)` — verified against real `index-v2` rows in `loose_consumer_forms_spec` (the corpus's citeas comes out as the BIPM-long style, which is the consumer's rendering choice, not pubid's). **(3) The guide's French body-prefixed long form** `Décision CIPM/111-10 (2022)` / `Recommandation JCRB/43-1 (2021)`: an alternative inside `committee_long_fr` (same capture name, so the same builder path — long form, language F). **The canonical French long form stays the connective spelling** (`Décision 101-1 du CIPM (2012)`) even though the guide's language-independent example prefers the body-prefixed one — the corpus freezes the connective render for what already parsed, and flipping the canonical would move frozen cases; that flip is an owner decision if the guide is adopted wholesale. **(4) SI Brochure guide forms**: the language code and the version are now OPTIONAL (`SI Brochure 9e v3.01 (2019/2024)` language-independent, `SI Brochure 9e (2019, E)` version-less — both byte round-trips with the canonical `BIPM SI Brochure ` lead), and the guide's French lead `Brochure sur le SI 9e …` parses as an alias of the `BIPM SI Brochure sur le SI …` canonical (note the lead alternation is `"BIPM SI Brochure" / "SI Brochure" / "Brochure sur le SI"` with the `["sur le SI" space]` phrase still optional after the first two — it just fails cleanly after the third). **(5) The corpus-cited ordinal-edition spelling** `SI Brochure 9th edition (2019)`: its own `si_brochure_ordinal_edition` rule (a separate branch, because the `edition` capture would otherwise bind `"9th"`); the builder strips the ordinal and appends `e`, so it normalizes onto `BIPM SI Brochure 9e (2019)`. **(6) Part numbers** (the guide's resource-specific-id numbering, `partnumbers` proposal, bipm-si-brochure#301): parts/subparts are dash forms inside `number` (`CIPM DECN 111-10-2`) and always parsed; the appendix/annex complement `CIPM DECN 112 A3[.5]` is the new optional ` A<n>[.<m>]` tail on `committee_short`, stored in the shared `appendix`/`annex` attributes (the MEP/guide tail's home), printed back byte-exactly by `appendix_segment`, folded into the URN identifier segment as `-a<n>[.<m>]` (`urn:bipm:cipm:decn:112-a3.5:2022` — unambiguous, because a bare number never carries letters; `UrnParser#split_appendix` splits it back), and joined into the MR number segment (`mr_slug` collapses the annex dot). It requires a number before it (`CIPM DECN A3` still fails). **URN deviations from the guide, deliberate**: the guide's own committee-example URNs (`urn:bipm:v1:cipm:resolution:95-2006:1`) carry a `v1` version segment and venue-qualified shapes the flavor's URNs never adopted (`urn:bipm:cipm:res:101-1:2012`, no `v1`, no venue segments — green for years, kept byte-identical); the new forms emit URNs under the EXISTING shape for the same reason, and the same applies to the brochure (`urn:bipm:si-brochure:<lang>:<edition>:<version>:<years>` vs the guide's `urn:bipm:v1:bipm:si-brochure:9:3.01:2024` — the guide strips the `e`/`v` and drops the language segment; adopting that is an index-affecting owner decision, not taken here). **One Ruby trap the URN fold exposed**: `id.number.to_s` on a String **attribute** returns the attribute itself, so building the folded segment by `<<` mutated `number` in place — every later `to_s`/`to_hash` of the same object then carried `-a3`; the fold is now interpolation, and `urn_spec` pins that a URN emission leaves `number` untouched. **The unmerged `si-brochure-id` proposal** (`SI Brochure 9`, `SI Brochure 9 (E)`, `SI Brochure 9 (2024) (F)` — bare-number edition) is NOT implemented: it conflicts with the merged guide's `9e` spelling. (hand-off: bipm-657-corpus-forms.)
15
+ - **Corpus-cited forms + guide printings (pubid#657)**: the metanorma-bipm fetch queue cites two spellings the grammar rejected, and the 2026-11 guide update adds printings of its own. All are **parse-in forms over the existing canonical identities** — the canonical render follows the guide, nothing that already parsed changed. **(1) The em-dash meeting variant** `CGPM -- Meeting 1 (1889)`: `meeting_en` gained `<group> -- Meeting <n>` and `<group> -- <n><ord> Meeting` branches (the corpus cites only the first; the second is the same reordering over the other word order). They build through the existing `Meeting` path, so the canonical render is `CGPM 1st Meeting (1889)`. **(2) The publisher-prefixed decision** `BIPM DECN CIPM/101-1 (2012, EN)` (and `, FR`): new `committee_prefixed` rule — a **literal `BIPM`** lead (the constant publisher, deliberately NOT the `group` table, which contains BIPM and would collide with the second `group` capture), `type_abbrev`, then the body-qualified id `{group}/{number}`. Builds through `build_committee` short-form, so it equals `CIPM DECN 101-1 (2012, E)` — verified against real `index-v2` rows in `loose_consumer_forms_spec` (the corpus's citeas comes out as the BIPM-long style, which is the consumer's rendering choice, not pubid's). **(3) The guide's French body-prefixed long form** `Décision CIPM/111-10 (2022)` / `Recommandation JCRB/43-1 (2021)`: an alternative inside `committee_long_fr` (same capture name, so the same builder path — long form, language F). **The canonical French long form stays the connective spelling** (`Décision 101-1 du CIPM (2012)`) even though the guide's language-independent example prefers the body-prefixed one — the corpus freezes the connective render for what already parsed, and flipping the canonical would move frozen cases; that flip is an owner decision if the guide is adopted wholesale. **(4) SI Brochure guide forms**: the language code and the version are now OPTIONAL (`SI Brochure 9e v3.01 (2019/2024)` language-independent, `SI Brochure 9e (2019, E)` version-less — both byte round-trips with the canonical `BIPM SI Brochure ` lead), and the guide's French lead `Brochure sur le SI 9e …` parses as an alias of the `BIPM SI Brochure sur le SI …` canonical (note the lead alternation is `"BIPM SI Brochure" / "SI Brochure" / "Brochure sur le SI"` with the `["sur le SI" space]` phrase still optional after the first two — it just fails cleanly after the third). **(5) The corpus-cited ordinal-edition spelling** `SI Brochure 9th edition (2019)`: its own `si_brochure_ordinal_edition` rule (a separate branch, because the `edition` capture would otherwise bind `"9th"`); the builder strips the ordinal and appends `e`, so it normalizes onto `BIPM SI Brochure 9e (2019)`. **(6) Part numbers** (the guide's resource-specific-id numbering, `partnumbers` proposal, bipm-si-brochure#301): parts/subparts are dash forms inside `number` (`CIPM DECN 111-10-2`) and always parsed; the appendix/annex complement `CIPM DECN 112 A3[.5]` is the new optional ` A<n>[.<m>]` tail on `committee_short`, stored in the shared `appendix`/`annex` attributes (the MEP/guide tail's home), printed back byte-exactly by `appendix_segment`, folded into the URN identifier segment as `-a<n>[.<m>]` (`urn:bipm:cipm:decn:112-a3.5:2022` — unambiguous, because a bare number never carries letters; `UrnParser#split_appendix` splits it back), and joined into the MR number segment (`mr_slug` collapses the annex dot). It requires a number before it (`CIPM DECN A3` still fails). **URN deviations from the guide, deliberate**: the guide's own committee-example URNs (`urn:bipm:v1:cipm:resolution:95-2006:1`) carry a `v1` version segment and venue-qualified shapes the flavor's URNs never adopted (`urn:bipm:cipm:res:101-1:2012`, no `v1`, no venue segments — green for years, kept byte-identical); the new forms emit URNs under the EXISTING shape for the same reason, and the same applies to the brochure (`urn:bipm:si-brochure:<lang>:<edition>:<version>:<years>` vs the guide's `urn:bipm:v1:bipm:si-brochure:9:3.01:2024` — the guide strips the `e`/`v` and drops the language segment; adopting that is an index-affecting owner decision, not taken here). *(Both halves superseded by the 2026-11-11 official-URN ruling, below.)* **One Ruby trap the URN fold exposed**: `id.number.to_s` on a String **attribute** returns the attribute itself, so building the folded segment by `<<` mutated `number` in place — every later `to_s`/`to_hash` of the same object then carried `-a3`; the fold is now interpolation, and `urn_spec` pins that a URN emission leaves `number` untouched. **The unmerged `si-brochure-id` proposal** (`SI Brochure 9`, `SI Brochure 9 (E)`, `SI Brochure 9 (2024) (F)` — bare-number edition) is NOT implemented: it conflicts with the merged guide's `9e` spelling. *(Superseded — adopted as parse-in aliases by the 2026-11-11 ruling, below.)* (hand-off: bipm-657-corpus-forms.)
16
+
17
+ - **The three 2026-11-11 owner rulings (official URNs, bare-number brochure editions, French-long flip)**: the owner ratified the guide-of-record wholesale, moving the official `urn:bipm:` face for the first time (a relaton-data-bipm re-crawl follows, accepted) and flipping one canonical render.
18
+ **(R1 — the `si-brochure-id` proposal adopted as parse-in aliases.** `SI Brochure 9`, `SI Brochure 9 (E)` and `SI Brochure 9 (2024) (F)` now parse, via the `si_brochure_bare_edition` grammar rule (the `si_brochure_ordinal_edition` model: a separate branch because the `edition` capture would bind the bare digit without the canonical `e`; the year and the language may each sit in their own parentheses, exactly the proposal's spellings). The builder appends the `e` — `9` → `9e`, the same normalization the ordinal spelling takes — so all three land on `BIPM SI Brochure 9e …`. **Two grammar/render consequences worth recording**: the canonical render of a year-less alias (`BIPM SI Brochure 9e`) must re-parse, so the brochure parenthetical is now OPTIONAL and accepts a **language-only paren** (`(E)` with no year — the main `si_brochure` rule's second alternative); and the renderer's paren segment had to stop printing unconditionally, because a nil `years` beside a language rendered the garbage `(, E)` — a live bug the year-less ordinal spelling already had on main (`SI Brochure 9th edition` → `BIPM SI Brochure 9e ()`). The paren now carries the year, the language or both, and stays off entirely when neither is named. Alias expectations live ONLY in `loose_consumer_forms_spec` (the ruling restates the normalizing-parses-never-in-pass rule); the three canonical faces gained byte-exact fixture lines.
19
+ **(R2 — the guide's official-URN shapes adopted.** Every `to_urn` now emits the `v1` face: committee `urn:bipm:v1:{group}:{type-word}:{identifier}[:{year}]` (the guide's `urn:bipm:v1:cipm:decision:110-17:2021` verbatim; the type segment is the lowercase ENGLISH NAME — recommendation/resolution/decision/action/statement — never the REC/RES/DECN abbreviation), meeting `urn:bipm:v1:{group}:meeting:{n}[:{year}]` (venue-type + venue-id as separate segments per the guide's component list; the proceedings example glues them as `meeting-26` and carries a publication year the flavor does not model), metrologia unchanged-shape-plus-`v1` (the guide's metrologia URN section is empty — as are MEP/Guide, which still raise: no landed URN, nothing to move), brochure `urn:bipm:v1:bipm:si-brochure:{edition-no-e}:{version-no-v}:{latest-year}` (the guide's pattern verbatim: language segment dropped, `9e`→`9`, `v3.01`→`3.01`, `2019/2024`→`2024` — the guide's "latest year of publication at the version"; the version segment stays empty when absent, per the guide's `…si-brochure:9::2019`). **Two guide examples do not fit the guide's own stated pattern** and are documented, not reproduced: `urn:bipm:v1:cipm:resolution:95-2006:1` puts a venue-id and the outcome ordinal where the pattern's identifier/date slots sit (its own git history edited it there from `95-2006:resolution:1`, which fit), and `urn:bipm:v1:cgpm:26:2018:resolution:1` carries the CGPM meeting number in a venue-id slot the flavor has no attribute for — the venue qualification this flavor holds lives inside the document number (`110-17`), so CGPM resolutions emit `urn:bipm:v1:cgpm:resolution:1:2018`. **The legacy faces stay parseable as documented parse-in aliases** (`UrnParser` branches on the leading `v1`): `urn:bipm:cipm:res:101-1:2012`, `urn:bipm:si-brochure:e:9e:v3.01:2019/2024`, `urn:bipm:metrologia:51:1:128`, `urn:bipm:cgpm:meeting:17:1983` and the empty-segment forms all rebuild the same identity whose `to_urn` is now the v1 face — no stored row or consumer-held URN stops resolving while the crawl lags; the emitter never produces them. The brochure alias parser also tightened two segments through `presence` (the legacy face's empty edition/version/years segments now read nil, matching what `to_hash` stores). The **pubid-scheme URN (`urn:pubid:bipm:…`) is untouched** — schema, atoms and emission byte-identical (0-diff verified).
20
+ **(R3 — the French-long canonical flipped to the guide's body-prefixed form.** `render_committee_long`'s F branch now prints `{name} {group}/{number} (year)` — `Décision CIPM/111-10 (2022)` — for every group, wholesale (the owner overrode the guide's own mixed pattern, which keeps the connective for CGPM resolutions). A **number-less** document has no id to qualify, so it keeps the connective spelling (`Statement de la CGPM (1889)`) — the guide's general French pattern. The connective spellings (`Décision 101-1 du CIPM (2012)`) and the group-leading word order (`CIPM Décision 101-1 (2012)`) are now normalizing aliases onto the same identity; `language: "F"` semantics unchanged. **Six byte-exact fixture lines moved** to the new canonical (recorded in the PR): the four CCL/CGPM/JCRB/CIPM connective lines became their body-prefixed spellings, the two that would have duplicated existing lines were replaced (one by the guide's own `Décision CIPM/111-10 (2022)` example, one dropped as now-identical), and the partial `Recommandation 1 du CCL` became `Recommandation CCL/1`. `to_hash` is unchanged by all three rulings — the flips are render/emitter faces only; verified by a full corpus replay (0 `to_hash` diffs, `to_pubid_urn` 0-diff).
16
21
 
17
22
  ## pubid-scheme URN (issue #670)
18
23
 
@@ -39,6 +39,8 @@ module Pubid
39
39
  build_si_brochure_section(data[:si_brochure_section])
40
40
  elsif data.key?(:si_brochure_ordinal_edition)
41
41
  build_si_brochure_ordinal_edition(data[:si_brochure_ordinal_edition])
42
+ elsif data.key?(:si_brochure_bare_edition)
43
+ build_si_brochure_bare_edition(data[:si_brochure_bare_edition])
42
44
  elsif data[:mep]
43
45
  build_mep(data[:mep])
44
46
  elsif data[:guide]
@@ -119,7 +121,7 @@ module Pubid
119
121
  number: node[:edition].to_s,
120
122
  edition: node[:edition].to_s,
121
123
  version: node[:version]&.to_s,
122
- years: node[:years].to_s,
124
+ years: node[:years]&.to_s,
123
125
  language: Identifier.normalize_language(node[:language]),
124
126
  )
125
127
  end
@@ -153,6 +155,23 @@ module Pubid
153
155
  )
154
156
  end
155
157
 
158
+ # The unmerged si-brochure-id proposal spells the edition as a bare
159
+ # number ("SI Brochure 9 (2024) (F)"); the guide's canonical edition
160
+ # carries the trailing "e", so the bare number normalizes onto it the
161
+ # same way the corpus-cited ordinal spelling does. The proposal puts the
162
+ # year and the language in separate parentheses; they map onto the same
163
+ # attributes the canonical form prints, and an absent year stays nil (a
164
+ # partial reference, like "SI Brochure 9th edition" without a year).
165
+ def build_si_brochure_bare_edition(node)
166
+ edition = "#{node[:bare_edition]}e"
167
+ Identifiers::SiBrochure.new(
168
+ number: edition,
169
+ edition: edition,
170
+ years: node[:year]&.to_s,
171
+ language: Identifier.normalize_language(node[:language]),
172
+ )
173
+ end
174
+
156
175
  # The Appendix/Annex/Part tail is the full-content spelling of the SAME
157
176
  # document, so it stays out of the key.
158
177
  def build_mep(node)
@@ -57,6 +57,15 @@ module Pubid
57
57
  def render_committee_long(id)
58
58
  if id.language == "F"
59
59
  name = Identifier::TYPE_NAME_FR[id.type_code]
60
+ # The guide's body-prefixed French pattern ("Décision CIPM/111-10
61
+ # (2022)") is the canonical long face: the group qualifies the
62
+ # resource-specific id with a slash. A number-less document has no
63
+ # id to qualify, so it keeps the connective spelling the guide's
64
+ # general French pattern uses ("Statement de la CGPM (1889)").
65
+ if id.number
66
+ return "#{name} #{id.group}/#{id.number}#{year_segment(id)}"
67
+ end
68
+
60
69
  conn = Identifier.french_connective(id.group)
61
70
  "#{name}#{number_segment(id)} #{conn} #{id.group}#{year_segment(id)}"
62
71
  else
@@ -91,12 +100,15 @@ module Pubid
91
100
 
92
101
  # The version is optional (the guide's "SI Brochure 9e (2019, E)" form)
93
102
  # and the language code too (the language-independent form), so both
94
- # segments are conditional.
103
+ # segments are conditional. The parens carry the year, the language or
104
+ # both, and stay off entirely when the reference names neither ("SI
105
+ # Brochure 9e", the bare-number alias without a year).
95
106
  phrase = id.language == "F" ? "sur le SI " : ""
96
107
  lang = id.language ? ", #{id.language}" : ""
108
+ tail = id.years ? "#{id.years}#{lang}" : id.language.to_s
97
109
  result = +"BIPM SI Brochure #{phrase}#{id.edition}"
98
110
  result << " #{id.version}" if id.version
99
- result << " (#{id.years}#{lang})"
111
+ result << " (#{tail})" if id.years || id.language
100
112
  result
101
113
  end
102
114
 
@@ -3,12 +3,50 @@
3
3
  module Pubid
4
4
  module Bipm
5
5
  # Emits a URN carrying the canonical (language/form-neutral) document
6
- # identity for each family:
7
- # committee: urn:bipm:<group>:<type>:<number>:<year> (empty number seg
8
- # when number-less)
9
- # meeting: urn:bipm:<group>:meeting:<number>:<year>
10
- # metrologia: urn:bipm:metrologia:<vol>[:<issue>[:<article>]]
11
- # brochure: urn:bipm:si-brochure:<lang>:<edition>:<version>:<years>
6
+ # identity for each family, in the guide-of-record's official shapes
7
+ # (bipm-editor-guides, sources/bipm-references-en.adoc, "URN" clauses):
8
+ #
9
+ # committee: urn:bipm:v1:<group>:<type-word>:<identifier>[:<year>]
10
+ # meeting: urn:bipm:v1:<group>:meeting:<number>[:<year>]
11
+ # metrologia: urn:bipm:v1:metrologia:<vol>[:<issue>[:<article>]]
12
+ # brochure: urn:bipm:v1:bipm:si-brochure:<edition>:[<version>]:<year>
13
+ #
14
+ # The guide's general pattern is
15
+ # `urn:bipm:{version}:{group}:{venue-type}:{venue-id}
16
+ # :{type}:{identifier}:{date}` with the venue slots optional. The
17
+ # flavor holds no venue attribute — the venue qualification lives
18
+ # inside the document number itself ("110-17" is the 110th meeting's
19
+ # decision 17) — so the venue slots stay unfilled and one shape per
20
+ # family falls out:
21
+ #
22
+ # * committee — the guide's `decision` example verbatim:
23
+ # `urn:bipm:v1:cipm:decision:110-17:2021`. The type segment is the
24
+ # English type name lowercased (resolution/recommendation/decision/
25
+ # action/statement), not the REC/RES/DECN abbreviation the previous
26
+ # face carried; the empty segment stays for the type-less bare MRA
27
+ # form. The guide's second committee example
28
+ # (`urn:bipm:v1:cipm:resolution:95-2006:1`) does not fit the stated
29
+ # pattern — it puts a venue-id (meeting 95, year 2006) and the
30
+ # outcome ordinal where the pattern's identifier/date slots sit
31
+ # (its own git history edited it there from `95-2006:resolution:1`,
32
+ # which DID fit). This flavor emits the pattern-conforming shape;
33
+ # a venue-qualified number flows through the identifier slot
34
+ # verbatim.
35
+ # * meeting — the venue-type `meeting` plus the venue-id, as separate
36
+ # segments per the guide's meeting-outcomes component list (its only
37
+ # accepted venue-type value). The guide's proceedings example glues
38
+ # them (`meeting-26`) and carries a proceedings type + publication
39
+ # year this flavor does not model.
40
+ # * metrologia — the guide defines no metrologia URN (its section is
41
+ # empty), so only the mandatory `v1` convention applies; the series
42
+ # shape is unchanged.
43
+ # * brochure — the guide's pattern verbatim: the `bipm` group lead,
44
+ # the edition without its `e` ("9e" → "9"), the version without its
45
+ # `v` ("v3.01" → "3.01"), the language segment dropped (the guide's
46
+ # URN is language-independent) and the year the latest the `years`
47
+ # range names ("2019/2024" → "2024", the guide's "latest year of
48
+ # publication at the version"). The version segment stays empty when
49
+ # absent, per the guide's `urn:bipm:v1:bipm:si-brochure:9::2019`.
12
50
  class UrnGenerator < Pubid::UrnGenerator::Base
13
51
  def generate
14
52
  case identifier
@@ -23,18 +61,29 @@ module Pubid
23
61
 
24
62
  private
25
63
 
64
+ # The v1 version segment leads every family; the venue-type/venue-id
65
+ # slots of the guide's general pattern stay unfilled because the
66
+ # flavor carries no venue attribute (see the class comment).
26
67
  def committee
27
68
  id = identifier
28
- # type_code is nil for the bare MRA form → empty type segment, mirroring
29
- # the empty-number-segment convention for number-less documents.
30
- parts = ["urn", "bipm", id.group.downcase,
31
- id.type_code.to_s.downcase, committee_number_segment(id)]
69
+ # type_code is nil for the bare MRA form → empty type segment,
70
+ # mirroring the empty-number-segment convention for number-less
71
+ # documents. The segment is the lowercase English type name the
72
+ # guide spells out ("decision"), never the REC/RES/DECN code.
73
+ parts = ["urn", "bipm", "v1", id.group.downcase,
74
+ committee_type_segment(id), committee_number_segment(id)]
32
75
  # A partial (date-less) reference drops the trailing year segment; a
33
76
  # number-less-but-dated doc keeps its empty number segment.
34
77
  parts << id.year if id.year
35
78
  parts.join(":")
36
79
  end
37
80
 
81
+ def committee_type_segment(id)
82
+ return "" unless id.type_code
83
+
84
+ Identifier::TYPE_NAME_EN[id.type_code].downcase
85
+ end
86
+
38
87
  # The appendix/annex complement folds into the identifier segment as
39
88
  # "-a{appendix}[.{annex}]". A bare number is digits and dashes only, so
40
89
  # the letter-prefixed tail is unambiguous on the way back (UrnParser
@@ -50,21 +99,38 @@ module Pubid
50
99
 
51
100
  def meeting
52
101
  id = identifier
53
- parts = ["urn", "bipm", id.group.downcase, "meeting", id.number.to_s]
102
+ parts = ["urn", "bipm", "v1", id.group.downcase, "meeting",
103
+ id.number.to_s]
54
104
  parts << id.year if id.year
55
105
  parts.join(":")
56
106
  end
57
107
 
58
108
  def metrologia
59
- parts = ["urn", "bipm", "metrologia", identifier.volume]
109
+ parts = ["urn", "bipm", "v1", "metrologia", identifier.volume]
60
110
  parts << identifier.issue if identifier.issue
61
111
  parts << identifier.article if identifier.article
62
112
  parts.join(":")
63
113
  end
64
114
 
65
115
  def si_brochure
66
- ["urn", "bipm", "si-brochure", identifier.language.to_s.downcase,
67
- identifier.edition, identifier.version, identifier.years].join(":")
116
+ id = identifier
117
+ ["urn", "bipm", "v1", "bipm", "si-brochure",
118
+ brochure_edition_segment(id), id.version.to_s.delete_prefix("v"),
119
+ brochure_year_segment(id)].join(":")
120
+ end
121
+
122
+ # The guide's edition-number carries no "e" ("Notice the absence of the
123
+ # letter e after the number"): "9e" emits "9". A variant (Appendix 3 /
124
+ # Concise / FAQ) or a bare section reference names no edition, so the
125
+ # segment is empty — the documented partial-reference bucket.
126
+ def brochure_edition_segment(id)
127
+ id.edition.to_s.delete_suffix("e")
128
+ end
129
+
130
+ # The guide's year is "the latest year of publication at the version":
131
+ # the "2019" of "2019", the "2024" of "2019/2024".
132
+ def brochure_year_segment(id)
133
+ id.years.to_s.split("/").last
68
134
  end
69
135
  end
70
136
  end