pubid 2.0.24 → 2.0.26

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd50bd4f50c75670cd2212192f3b267c7b246e0bd46b3d7c8a8d191c3d57ed2d
4
- data.tar.gz: 99718c01c70282d9c28a62064c822b2c86f4856a9d3097f6ca7e67c930221789
3
+ metadata.gz: ef65c4cbf5afc5635f55300fc57894462ecc211d2dd51d2c2605dafda75a84a3
4
+ data.tar.gz: 89c5dccd912343c12ea157220089f7b1e0cf708abefe45e63c30c3bf712177e8
5
5
  SHA512:
6
- metadata.gz: dd66c8243817ea77c54956e963cb1ea469c90728ea3e84336c18fb7c010beb961c069242a5acb439892b99d2ae1a4e44f4a6877cab56a986eefd6b0a5784907a
7
- data.tar.gz: 74f3abb03d7785b95678b05ce0615fe16e21c2ebb67228014424c254e95ab3e37dc17dd86bbb71dcd6b8279152ed620ac386ecda4b3f2b429feb4acec8967b33
6
+ metadata.gz: 135acc1bf9bd56785e0c861db78c0cad0b5026527ca6798ba1e5df7f9d2a19124211cc2b3583db2b332bcd7fc29300736b28c44b0dbbbdd482742607df2f2ddc
7
+ data.tar.gz: ef9dc843feed906ae25c564839c6d710c30e3e4a3d7fb1e96fc4376ca73551deaade688e3bf7ceb825fd93292962a4db722f7994b4036a73c61d99e91a4f5626
@@ -1 +1 @@
1
- {"bindings":null,"version":"0.1.0","grammar":"PubidEtsiPfs","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Str":{"pattern":"ETSI "}},{"Entity":{"atom":18}},{"Str":{"pattern":"I-ETS"}},{"Str":{"pattern":"TCRTR"}},{"Str":{"pattern":"GTS"}},{"Str":{"pattern":"ETR"}},{"Str":{"pattern":"ETS"}},{"Str":{"pattern":"TBR"}},{"Str":{"pattern":"NET"}},{"Str":{"pattern":"EN"}},{"Str":{"pattern":"ES"}},{"Str":{"pattern":"EG"}},{"Str":{"pattern":"TS"}},{"Str":{"pattern":"GR"}},{"Str":{"pattern":"GS"}},{"Str":{"pattern":"SR"}},{"Str":{"pattern":"TR"}},{"Alternative":{"atoms":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}},{"Named":{"name":"type","atom":17}},{"Str":{"pattern":" "}},{"Entity":{"atom":54}},{"Str":{"pattern":"GSM"}},{"Str":{"pattern":" "}},{"Entity":{"atom":24}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":23,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Entity":{"atom":24}},{"Repetition":{"atom":27,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[21,22,25,26,28]}},{"Entity":{"atom":24}},{"Repetition":{"atom":30,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Entity":{"atom":24}},{"Repetition":{"atom":33,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[31,32,34]}},{"Entity":{"atom":40}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Alternative":{"atoms":[37,38,39]}},{"Repetition":{"atom":36,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"-"}},{"Entity":{"atom":40}},{"Repetition":{"atom":43,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[42,44]}},{"Repetition":{"atom":45,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" "}},{"Entity":{"atom":24}},{"Repetition":{"atom":48,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[41,46,47,49]}},{"Entity":{"atom":24}},{"Repetition":{"atom":51,"min":1,"max":null,"tag":"Repetition"}},{"Alternative":{"atoms":[29,35,50,52]}},{"Named":{"name":"number","atom":53}},{"Sequence":{"atoms":[0,1,19,20]}},{"Str":{"pattern":"ETSI "}},{"Entity":{"atom":18}},{"Str":{"pattern":" "}},{"Entity":{"atom":54}},{"Str":{"pattern":" "}},{"Entity":{"atom":64}},{"Entity":{"atom":24}},{"Repetition":{"atom":62,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"minor","atom":63}},{"Sequence":{"atoms":[60,61]}},{"Repetition":{"atom":65,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":73}},{"Str":{"pattern":"-"}},{"Entity":{"atom":40}},{"Repetition":{"atom":69,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[68,70]}},{"Repetition":{"atom":71,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"parts","atom":72}},{"Repetition":{"atom":67,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":83}},{"Str":{"pattern":"/A"}},{"Str":{"pattern":"/C"}},{"Alternative":{"atoms":[76,77]}},{"Entity":{"atom":24}},{"Repetition":{"atom":79,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[78,80]}},{"Repetition":{"atom":81,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"supplements","atom":82}},{"Repetition":{"atom":75,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" V"}},{"Entity":{"atom":92}},{"Entity":{"atom":90}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[.]"}},{"Alternative":{"atoms":[88,89]}},{"Repetition":{"atom":87,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"version","atom":91}},{"Sequence":{"atoms":[85,86]}},{"Str":{"pattern":" ed."}},{"Entity":{"atom":98}},{"Entity":{"atom":24}},{"Repetition":{"atom":96,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"edition","atom":97}},{"Sequence":{"atoms":[94,95]}},{"Alternative":{"atoms":[93,99]}},{"Repetition":{"atom":100,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" ("}},{"Entity":{"atom":106}},{"Entity":{"atom":24}},{"Repetition":{"atom":104,"min":4,"max":4,"tag":"Repetition"}},{"Named":{"name":"year","atom":105}},{"Str":{"pattern":"-"}},{"Entity":{"atom":111}},{"Entity":{"atom":24}},{"Repetition":{"atom":109,"min":2,"max":2,"tag":"Repetition"}},{"Named":{"name":"month","atom":110}},{"Str":{"pattern":")"}},{"Sequence":{"atoms":[102,103,107,108,112]}},{"Repetition":{"atom":113,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[56,57,58,59,66,74,84,101,114]}},{"Alternative":{"atoms":[55,115]}}],"root":116},"bindings":[{"capture":"type","path":"type","type":"string","card":null,"preprocess":null},{"capture":"number","path":"number","type":"string","card":null,"preprocess":null},{"capture":"minor","path":"minor","type":"string","card":null,"preprocess":null},{"capture":"parts","path":"parts","type":"string","card":null,"preprocess":null},{"capture":"supplements","path":"supplements","type":"string","card":null,"preprocess":null},{"capture":"version","path":"version","type":"string","card":null,"preprocess":null},{"capture":"edition","path":"edition","type":"string","card":null,"preprocess":null},{"capture":"year","path":"year","type":"string","card":null,"preprocess":null},{"capture":"month","path":"month","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)"]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"ETSI EG 200 053 V1.5.1 (2004-06)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EN 300 175","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EN 300 175-2","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EN 300 019-1-0 V2.1.2 (2003-09)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI TS 103 702","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EG 201 880 V1.2.1","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI GTS GSM 02.01 V5.5.0 (1999-08)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI I-ETS 300 003 ed.1 (1991-08)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI ETR 108/A1 ed.1 (1995-08)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)","expect":{}},{"entry":"identifier","kind":"reject","input":"nonsense","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI XX 100","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 V","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 V1.2.x","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 (","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 (2004-6)","expect":{}},{"entry":"identifier","kind":"example","input":"ETSI EG 200 053 V1.5.1 (2004-06)","expect":{"type":"EG","number":"200 053","version":"1.5.1","year":"2004","month":"06"}},{"entry":"identifier","kind":"example","input":"ETSI EN 300 175","expect":{"type":"EN","number":"300 175"}},{"entry":"identifier","kind":"example","input":"ETSI EN 300 175-2","expect":{"type":"EN","number":"300 175","parts":"-2"}},{"entry":"identifier","kind":"example","input":"ETSI EN 300 019-1-0 V2.1.2 (2003-09)","expect":{"type":"EN","number":"300 019","parts":"-1-0","version":"2.1.2","year":"2003","month":"09"}},{"entry":"identifier","kind":"example","input":"ETSI TS 103 702","expect":{"type":"TS","number":"103 702"}},{"entry":"identifier","kind":"example","input":"ETSI EG 201 880 V1.2.1","expect":{"type":"EG","number":"201 880","version":"1.2.1"}},{"entry":"identifier","kind":"example","input":"ETSI GTS GSM 02.01 V5.5.0 (1999-08)","expect":{"type":"GTS","number":"GSM 02.01","version":"5.5.0","year":"1999","month":"08"}},{"entry":"identifier","kind":"example","input":"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)","expect":{"type":"GR","number":"NFV-EVE 008","version":"3.1.1","year":"2017","month":"12"}},{"entry":"identifier","kind":"example","input":"ETSI I-ETS 300 003 ed.1 (1991-08)","expect":{"type":"I-ETS","number":"300 003","edition":"1","year":"1991","month":"08"}},{"entry":"identifier","kind":"example","input":"ETSI ETR 108/A1 ed.1 (1995-08)","expect":{"type":"ETR","number":"108","supplements":"/A1","edition":"1","year":"1995","month":"08"}},{"entry":"identifier","kind":"example","input":"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)","expect":{"type":"ETS","number":"300 092","parts":"-1","supplements":"/A1/C1","edition":"1","year":"1994","month":"04"}}],"docs":{},"source":"# PFS-generated grammar — DO NOT EDIT BY HAND.\n# Source: definitions/etsi.pfs (pfs:1) sha256:9343088bf087b55ea790057e6be0bbebe2272407ee81f035b52e5f9a4d5c7b76\n# Compiler: pubid/grammar/pfs 0.1.0\n# Flavor key: etsi_pfs (grammar PubidEtsiPfs)\n# Regenerate: ruby scripts/pfs compile definitions/etsi.pfs --as etsi_pfs\n\ngrammar PubidEtsiPfs version \"0.1.0\" edition 2 {\n\n # Grammar fragments: named lexical terms declared under `grammar:`.\n digit = %x30-39\n alnum_char = %x41-5A / %x61-7A / %x30-39\n version_char = %x30-39 / %x2E\n\n # Fields: one capture rule per declared field; the whole pattern\n # is the capture.\n etsi_type = (\"I-ETS\" / \"TCRTR\" / \"GTS\" / \"ETR\" / \"ETS\" / \"TBR\" / \"NET\" / \"EN\" / \"ES\" / \"EG\" / \"TS\" / \"GR\" / \"GS\" / \"SR\" / \"TR\") as type\n etsi_number = ((\"GSM\" \" \" 1*digit \".\" 1*digit) / (1*digit \".\" 1*digit) / (1*alnum_char [(\"-\" 1*alnum_char)] \" \" 1*digit) / (1*digit)) as number\n etsi_minor = (1*digit) as minor\n etsi_parts = (1*(\"-\" 1*alnum_char)) as parts\n etsi_supplements = (1*((\"/A\" / \"/C\") 1*digit)) as supplements\n etsi_version = (1*version_char) as version\n etsi_edition = (1*digit) as edition\n etsi_year = (4digit) as year\n etsi_month = (2digit) as month\n\n # Identifier rule: the canonical template first, then the\n # declared alternates (parse-in spellings; they normalize to the\n # canonical render face).\n identifier = {\n \"ETSI \" etsi_type \" \" etsi_number\n / \"ETSI \" etsi_type \" \" etsi_number [\" \" etsi_minor] [etsi_parts] [etsi_supplements] [( \" V\" etsi_version / \" ed.\" etsi_edition )] [\" (\" etsi_year \"-\" etsi_month \")\"]\n }\n entry identifier: identifier\n\n # Typed model bindings: every bound capture lands on the\n # consumer model's field of the same name.\n bindings identifier {\n type -> type (string)\n number -> number (string)\n minor -> minor (string)\n parts -> parts (string)\n supplements -> supplements (string)\n version -> version (string)\n edition -> edition (string)\n year -> year (string)\n month -> month (string)\n }\n\n # REQUIRED examples from the definition — accepts, rejects, and\n # the capture expectations the compiler replayed at compile time.\n test {\n accept \"ETSI EG 200 053 V1.5.1 (2004-06)\"\n accept \"ETSI EN 300 175\"\n accept \"ETSI EN 300 175-2\"\n accept \"ETSI EN 300 019-1-0 V2.1.2 (2003-09)\"\n accept \"ETSI TS 103 702\"\n accept \"ETSI EG 201 880 V1.2.1\"\n accept \"ETSI GTS GSM 02.01 V5.5.0 (1999-08)\"\n accept \"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)\"\n accept \"ETSI I-ETS 300 003 ed.1 (1991-08)\"\n accept \"ETSI ETR 108/A1 ed.1 (1995-08)\"\n accept \"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)\"\n reject \"nonsense\"\n reject \"ETSI\"\n reject \"ETSI EN\"\n reject \"ETSI XX 100\"\n reject \"ETSI EN 300 175 V\"\n reject \"ETSI EN 300 175 V1.2.x\"\n reject \"ETSI EN 300 175 (\"\n reject \"ETSI EN 300 175 (2004-6)\"\n example \"ETSI EG 200 053 V1.5.1 (2004-06)\" { type: \"EG\" number: \"200 053\" version: \"1.5.1\" year: \"2004\" month: \"06\" }\n example \"ETSI EN 300 175\" { type: \"EN\" number: \"300 175\" }\n example \"ETSI EN 300 175-2\" { type: \"EN\" number: \"300 175\" parts: \"-2\" }\n example \"ETSI EN 300 019-1-0 V2.1.2 (2003-09)\" { type: \"EN\" number: \"300 019\" parts: \"-1-0\" version: \"2.1.2\" year: \"2003\" month: \"09\" }\n example \"ETSI TS 103 702\" { type: \"TS\" number: \"103 702\" }\n example \"ETSI EG 201 880 V1.2.1\" { type: \"EG\" number: \"201 880\" version: \"1.2.1\" }\n example \"ETSI GTS GSM 02.01 V5.5.0 (1999-08)\" { type: \"GTS\" number: \"GSM 02.01\" version: \"5.5.0\" year: \"1999\" month: \"08\" }\n example \"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)\" { type: \"GR\" number: \"NFV-EVE 008\" version: \"3.1.1\" year: \"2017\" month: \"12\" }\n example \"ETSI I-ETS 300 003 ed.1 (1991-08)\" { type: \"I-ETS\" number: \"300 003\" edition: \"1\" year: \"1991\" month: \"08\" }\n example \"ETSI ETR 108/A1 ed.1 (1995-08)\" { type: \"ETR\" number: \"108\" supplements: \"/A1\" edition: \"1\" year: \"1995\" month: \"08\" }\n example \"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)\" { type: \"ETS\" number: \"300 092\" parts: \"-1\" supplements: \"/A1/C1\" edition: \"1\" year: \"1994\" month: \"04\" }\n }\n}\n","source_hash":"f5e47bb403d9bff4737d8a200f08c71f8029df12f96c56e9774b722908bfa853","unicode_tables":"onigmo (Ruby 3.4.8)","compiler_version":"1.3.126","edition":2,"semantics_hash":"sha256:204a90b72aa2ea5fc2249622bd61c052b4ba5d1ebf732a2c2b57669c8de24393","checksum":"sha256:d861e040f36868dcef43c6547c5791979d4832d1ec1ca6bc310bdad82ea0e930"}
1
+ {"bindings":null,"version":"0.1.0","grammar":"PubidEtsiPfs","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Str":{"pattern":"ETSI "}},{"Entity":{"atom":18}},{"Str":{"pattern":"I-ETS"}},{"Str":{"pattern":"TCRTR"}},{"Str":{"pattern":"GTS"}},{"Str":{"pattern":"ETR"}},{"Str":{"pattern":"ETS"}},{"Str":{"pattern":"TBR"}},{"Str":{"pattern":"NET"}},{"Str":{"pattern":"EN"}},{"Str":{"pattern":"ES"}},{"Str":{"pattern":"EG"}},{"Str":{"pattern":"TS"}},{"Str":{"pattern":"GR"}},{"Str":{"pattern":"GS"}},{"Str":{"pattern":"SR"}},{"Str":{"pattern":"TR"}},{"Alternative":{"atoms":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}},{"Named":{"name":"type","atom":17}},{"Str":{"pattern":" "}},{"Entity":{"atom":54}},{"Str":{"pattern":"GSM"}},{"Str":{"pattern":" "}},{"Entity":{"atom":24}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":23,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Entity":{"atom":24}},{"Repetition":{"atom":27,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[21,22,25,26,28]}},{"Entity":{"atom":24}},{"Repetition":{"atom":30,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Entity":{"atom":24}},{"Repetition":{"atom":33,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[31,32,34]}},{"Entity":{"atom":40}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Alternative":{"atoms":[37,38,39]}},{"Repetition":{"atom":36,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"-"}},{"Entity":{"atom":40}},{"Repetition":{"atom":43,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[42,44]}},{"Repetition":{"atom":45,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" "}},{"Entity":{"atom":24}},{"Repetition":{"atom":48,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[41,46,47,49]}},{"Entity":{"atom":24}},{"Repetition":{"atom":51,"min":1,"max":null,"tag":"Repetition"}},{"Alternative":{"atoms":[29,35,50,52]}},{"Named":{"name":"number","atom":53}},{"Str":{"pattern":" "}},{"Entity":{"atom":59}},{"Entity":{"atom":24}},{"Repetition":{"atom":57,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"minor","atom":58}},{"Sequence":{"atoms":[55,56]}},{"Repetition":{"atom":60,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":70}},{"Str":{"pattern":"-"}},{"Entity":{"atom":67}},{"Entity":{"atom":40}},{"Repetition":{"atom":65,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"part","atom":66}},{"Sequence":{"atoms":[63,64]}},{"Repetition":{"atom":68,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"parts","atom":69}},{"Repetition":{"atom":62,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":87}},{"Entity":{"atom":85}},{"Str":{"pattern":"/"}},{"Entity":{"atom":79}},{"Str":{"pattern":"A"}},{"Str":{"pattern":"C"}},{"Alternative":{"atoms":[76,77]}},{"Named":{"name":"supplement_kind","atom":78}},{"Entity":{"atom":83}},{"Entity":{"atom":24}},{"Repetition":{"atom":81,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"supplement_number","atom":82}},{"Sequence":{"atoms":[74,75,80]}},{"Named":{"name":"supplement","atom":84}},{"Repetition":{"atom":73,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"supplements","atom":86}},{"Str":{"pattern":" V"}},{"Entity":{"atom":95}},{"Entity":{"atom":93}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[.]"}},{"Alternative":{"atoms":[91,92]}},{"Repetition":{"atom":90,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"version","atom":94}},{"Sequence":{"atoms":[88,89]}},{"Str":{"pattern":" ed."}},{"Entity":{"atom":101}},{"Entity":{"atom":24}},{"Repetition":{"atom":99,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"edition","atom":100}},{"Sequence":{"atoms":[97,98]}},{"Alternative":{"atoms":[96,102]}},{"Repetition":{"atom":103,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" ("}},{"Entity":{"atom":109}},{"Entity":{"atom":24}},{"Repetition":{"atom":107,"min":4,"max":4,"tag":"Repetition"}},{"Named":{"name":"year","atom":108}},{"Str":{"pattern":"-"}},{"Entity":{"atom":114}},{"Entity":{"atom":24}},{"Repetition":{"atom":112,"min":2,"max":2,"tag":"Repetition"}},{"Named":{"name":"month","atom":113}},{"Str":{"pattern":")"}},{"Sequence":{"atoms":[105,106,110,111,115]}},{"Repetition":{"atom":116,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[0,1,19,20,61,71,72,104,117]}},{"Str":{"pattern":"ETSI "}},{"Entity":{"atom":18}},{"Str":{"pattern":" "}},{"Entity":{"atom":54}},{"Str":{"pattern":" "}},{"Entity":{"atom":59}},{"Sequence":{"atoms":[123,124]}},{"Repetition":{"atom":125,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":70}},{"Repetition":{"atom":127,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":87}},{"Repetition":{"atom":129,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" V"}},{"Entity":{"atom":95}},{"Sequence":{"atoms":[131,132]}},{"Str":{"pattern":" ed."}},{"Entity":{"atom":101}},{"Sequence":{"atoms":[134,135]}},{"Alternative":{"atoms":[133,136]}},{"Repetition":{"atom":137,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" ("}},{"Entity":{"atom":109}},{"Str":{"pattern":"-"}},{"Entity":{"atom":114}},{"Str":{"pattern":")"}},{"Sequence":{"atoms":[139,140,141,142,143]}},{"Repetition":{"atom":144,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[119,120,121,122,126,128,130,138,145]}},{"Alternative":{"atoms":[118,146]}}],"root":147},"bindings":[{"capture":"type","path":"type","type":"string","card":null,"preprocess":null},{"capture":"number","path":"number","type":"string","card":null,"preprocess":null},{"capture":"minor","path":"minor","type":"string","card":null,"preprocess":null},{"capture":"part","path":"part","type":"string","card":null,"preprocess":null},{"capture":"part","path":"parts[].part","type":"string","card":null,"preprocess":null},{"capture":"version","path":"version","type":"string","card":null,"preprocess":null},{"capture":"edition","path":"edition","type":"string","card":null,"preprocess":null},{"capture":"year","path":"year","type":"string","card":null,"preprocess":null},{"capture":"month","path":"month","type":"string","card":null,"preprocess":null},{"capture":"supplement","path":"supplements[].supplement","type":"raw","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)"]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"ETSI EG 200 053 V1.5.1 (2004-06)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EN 300 175","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EN 300 175-2","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EN 300 019-1-0 V2.1.2 (2003-09)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI TS 103 702","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EG 201 880 V1.2.1","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI GTS GSM 02.01 V5.5.0 (1999-08)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI I-ETS 300 003 ed.1 (1991-08)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI ETR 108/A1 ed.1 (1995-08)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)","expect":{}},{"entry":"identifier","kind":"reject","input":"nonsense","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI XX 100","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 V","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 V1.2.x","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 (","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 (2004-6)","expect":{}},{"entry":"identifier","kind":"example","input":"ETSI EG 200 053 V1.5.1 (2004-06)","expect":{"type":"EG","number":"200 053","version":"1.5.1","year":"2004","month":"06"}},{"entry":"identifier","kind":"example","input":"ETSI EN 300 175","expect":{"type":"EN","number":"300 175"}},{"entry":"identifier","kind":"example","input":"ETSI EN 300 175-2","expect":{"type":"EN","number":"300 175","part":"2"}},{"entry":"identifier","kind":"example","input":"ETSI EN 300 019-1-0 V2.1.2 (2003-09)","expect":{"type":"EN","number":"300 019","part":"1","version":"2.1.2","year":"2003","month":"09"}},{"entry":"identifier","kind":"example","input":"ETSI TS 103 702","expect":{"type":"TS","number":"103 702"}},{"entry":"identifier","kind":"example","input":"ETSI EG 201 880 V1.2.1","expect":{"type":"EG","number":"201 880","version":"1.2.1"}},{"entry":"identifier","kind":"example","input":"ETSI GTS GSM 02.01 V5.5.0 (1999-08)","expect":{"type":"GTS","number":"GSM 02.01","version":"5.5.0","year":"1999","month":"08"}},{"entry":"identifier","kind":"example","input":"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)","expect":{"type":"GR","number":"NFV-EVE 008","version":"3.1.1","year":"2017","month":"12"}},{"entry":"identifier","kind":"example","input":"ETSI I-ETS 300 003 ed.1 (1991-08)","expect":{"type":"I-ETS","number":"300 003","edition":"1","year":"1991","month":"08"}},{"entry":"identifier","kind":"example","input":"ETSI ETR 108/A1 ed.1 (1995-08)","expect":{"type":"ETR","number":"108","edition":"1","year":"1995","month":"08"}},{"entry":"identifier","kind":"example","input":"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)","expect":{"type":"ETS","number":"300 092","part":"1","edition":"1","year":"1994","month":"04"}}],"docs":{},"source":"# PFS-generated grammar — DO NOT EDIT BY HAND.\n# Source: definitions/etsi.pfs (pfs:1) sha256:490ba9435f44dbd2d59429ae6d9527909c39444f19fec5a33a8cad91e89e3581\n# Compiler: pubid/grammar/pfs 0.1.0\n# Flavor key: etsi_pfs (grammar PubidEtsiPfs)\n# Regenerate: ruby scripts/pfs compile definitions/etsi.pfs --as etsi_pfs\n\ngrammar PubidEtsiPfs version \"0.1.0\" edition 2 {\n\n # Grammar fragments: named lexical terms declared under `grammar:`.\n digit = %x30-39\n alnum_char = %x41-5A / %x61-7A / %x30-39\n version_char = %x30-39 / %x2E\n\n # Fields: one capture rule per declared field; the whole pattern\n # is the capture.\n etsi_type = (\"I-ETS\" / \"TCRTR\" / \"GTS\" / \"ETR\" / \"ETS\" / \"TBR\" / \"NET\" / \"EN\" / \"ES\" / \"EG\" / \"TS\" / \"GR\" / \"GS\" / \"SR\" / \"TR\") as type\n etsi_number = ((\"GSM\" \" \" 1*digit \".\" 1*digit) / (1*digit \".\" 1*digit) / (1*alnum_char [(\"-\" 1*alnum_char)] \" \" 1*digit) / (1*digit)) as number\n etsi_minor = (1*digit) as minor\n etsi_part = (1*alnum_char) as part\n etsi_parts = (1*(\"-\" etsi_part)) as parts\n etsi_version = (1*version_char) as version\n etsi_edition = (1*digit) as edition\n etsi_year = (4digit) as year\n etsi_month = (2digit) as month\n\n # Supplement run (compiled from the supplements declaration):\n # each item binds its kind face and ordinal; the builder folds\n # the run into the declared wrapper classes.\n etsi_supplement_kind = (\"A\" / \"C\") as supplement_kind\n etsi_supplement_number = (1*digit) as supplement_number\n etsi_supplement = ( \"/\" etsi_supplement_kind etsi_supplement_number ) as supplement\n etsi_supplements = (1*etsi_supplement) as supplements\n\n # Identifier rule: the canonical template first, then the\n # declared alternates (parse-in spellings; they normalize to the\n # canonical render face).\n identifier = {\n \"ETSI \" etsi_type \" \" etsi_number [\" \" etsi_minor] [etsi_parts] etsi_supplements [( \" V\" etsi_version / \" ed.\" etsi_edition )] [\" (\" etsi_year \"-\" etsi_month \")\"]\n / \"ETSI \" etsi_type \" \" etsi_number [\" \" etsi_minor] [etsi_parts] [etsi_supplements] [( \" V\" etsi_version / \" ed.\" etsi_edition )] [\" (\" etsi_year \"-\" etsi_month \")\"]\n }\n entry identifier: identifier\n\n # Typed model bindings: every bound capture lands on the\n # consumer model's field of the same name.\n bindings identifier {\n type -> type (string)\n number -> number (string)\n minor -> minor (string)\n part -> part (string)\n part -> parts[].part (string)\n version -> version (string)\n edition -> edition (string)\n year -> year (string)\n month -> month (string)\n supplement -> supplements[].supplement (raw)\n }\n\n # REQUIRED examples from the definition — accepts, rejects, and\n # the capture expectations the compiler replayed at compile time.\n test {\n accept \"ETSI EG 200 053 V1.5.1 (2004-06)\"\n accept \"ETSI EN 300 175\"\n accept \"ETSI EN 300 175-2\"\n accept \"ETSI EN 300 019-1-0 V2.1.2 (2003-09)\"\n accept \"ETSI TS 103 702\"\n accept \"ETSI EG 201 880 V1.2.1\"\n accept \"ETSI GTS GSM 02.01 V5.5.0 (1999-08)\"\n accept \"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)\"\n accept \"ETSI I-ETS 300 003 ed.1 (1991-08)\"\n accept \"ETSI ETR 108/A1 ed.1 (1995-08)\"\n accept \"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)\"\n reject \"nonsense\"\n reject \"ETSI\"\n reject \"ETSI EN\"\n reject \"ETSI XX 100\"\n reject \"ETSI EN 300 175 V\"\n reject \"ETSI EN 300 175 V1.2.x\"\n reject \"ETSI EN 300 175 (\"\n reject \"ETSI EN 300 175 (2004-6)\"\n example \"ETSI EG 200 053 V1.5.1 (2004-06)\" { type: \"EG\" number: \"200 053\" version: \"1.5.1\" year: \"2004\" month: \"06\" }\n example \"ETSI EN 300 175\" { type: \"EN\" number: \"300 175\" }\n example \"ETSI EN 300 175-2\" { type: \"EN\" number: \"300 175\" part: \"2\" }\n example \"ETSI EN 300 019-1-0 V2.1.2 (2003-09)\" { type: \"EN\" number: \"300 019\" part: \"1\" version: \"2.1.2\" year: \"2003\" month: \"09\" }\n example \"ETSI TS 103 702\" { type: \"TS\" number: \"103 702\" }\n example \"ETSI EG 201 880 V1.2.1\" { type: \"EG\" number: \"201 880\" version: \"1.2.1\" }\n example \"ETSI GTS GSM 02.01 V5.5.0 (1999-08)\" { type: \"GTS\" number: \"GSM 02.01\" version: \"5.5.0\" year: \"1999\" month: \"08\" }\n example \"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)\" { type: \"GR\" number: \"NFV-EVE 008\" version: \"3.1.1\" year: \"2017\" month: \"12\" }\n example \"ETSI I-ETS 300 003 ed.1 (1991-08)\" { type: \"I-ETS\" number: \"300 003\" edition: \"1\" year: \"1991\" month: \"08\" }\n example \"ETSI ETR 108/A1 ed.1 (1995-08)\" { type: \"ETR\" number: \"108\" edition: \"1\" year: \"1995\" month: \"08\" }\n example \"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)\" { type: \"ETS\" number: \"300 092\" part: \"1\" edition: \"1\" year: \"1994\" month: \"04\" }\n }\n}\n","source_hash":"12923bc0c9d666d9c01feb4667bce45e7a3acb7b221bcdc59b4886ecbeda8f92","unicode_tables":"onigmo (Ruby 3.4.8)","compiler_version":"1.3.126","edition":2,"semantics_hash":"sha256:1078102d9883a5c7906a15119592a43447800ee5aaca2bbf060ac6394a089aa3","checksum":"sha256:bea8fc40624aa907dd59befc802bf84ee56591ed0130437c2d0fe6065ebaf3d6"}
@@ -7,8 +7,8 @@
7
7
 
8
8
  def build(data)
9
9
  Identifiers::Resource.new(
10
- prefix: data[:prefix],
11
- suffix: data[:suffix],
10
+ prefix: data[:prefix],
11
+ suffix: data[:suffix],
12
12
  )
13
13
  end
14
14
  end
@@ -39,57 +39,65 @@ Measured surface (see the spec header for the numbers and their meaning):
39
39
  (all 15 type tokens; the four number shapes GSM/dotted/complex/simple;
40
40
  minor; the parts run; the /A//C supplement runs; `V`-versions and
41
41
  `ed.N`; the `(YYYY-MM)` date).
42
- - **to_hash and to_pubid_urn byte parity: 18,945/24,718** — exactly the
43
- rows carrying none of the three constructs pfs:1 cannot declare. Each
44
- gap is pinned per construct in the spec, so compiler progress turns the
45
- pin red:
46
- 1. **collection bindings** — the coded model stores `["1","0"]` (the
47
- `part -> parts[].part` binding); the compiled capture is the printed
48
- dash run `"-1-0"`.
49
- 2. **the edition face** — the coded model folds `ed.N` into
50
- `Version(version: "N", is_edition: true)`; the compiled hash carries
51
- `edition: "N"`.
52
- 3. **supplement wrappers** — the coded model wraps `/A1` `/C1` as
53
- Amendment/Corrigendum classes with an Integer ordinal delegating to
54
- their base; the compiled row stays a flat etsi-standard carrying the
55
- printed run `"/A1"`.
56
- - **to_s** renders the canonical CORE face (`ETSI <type> <number>`) —
57
- pfs:1 render faces cannot render optionals, so every corpus row (all of
58
- which carry version/ed + date) renders shorter; bare ids agree
59
- byte-for-byte.
60
- - **to_urn** is a compiler gap (the coded generator is hand-written Ruby
61
- composing `urn:etsi:{type}:{code}:{version}:{date}`; the generated
62
- generator pins the namespace only). Note the CODED generator raises
63
- `ArgumentError` on months 08/09 (`format('%02d', "08")` — the zero-padded
64
- string reads as an invalid octal); pre-existing on main, pinned by the
65
- parity spec.
66
- - **to_mr_string** is a compiler gap (the coded slug reads the flavor's
67
- `mr_*` hooks — type/parts/edition/date segments; the base hooks find
68
- none of them on the compiled columns).
42
+ - **to_hash and to_pubid_urn byte parity: 24,718/24,718** (phase 3
43
+ closed all three constructs; the former per-construct pins are now
44
+ parity rows):
45
+ 1. **collection bindings** (3,772 rows) — `parts` is a `kind: list`
46
+ field binding its `part` item per repetition; the compiled model
47
+ stores the coded `["1","0"]` array, dropped when empty.
48
+ 2. **the edition face** (1,833 rows) — the `ed.N` capture folds onto
49
+ `version` + `is_edition: true` (the coded Version component shape).
50
+ 3. **supplement wrappers** (168 rows) — the `supplements:` declaration
51
+ folds the printed run into SYNTHESIZED Amendment/Corrigendum classes
52
+ over the base standard, outermost last ("/A1/C1" is Corrigendum over
53
+ Amendment over the standard).
54
+ Trajectory: 18,945 → 22,717 (collections) → 24,718 (fold + wrappers).
55
+ - **to_s** renders the full printed face — the canonical template's
56
+ presence-guarded optionals and the V/ed alternated tail render what
57
+ the input carried (the conditional-render segment feature).
58
+ - **to_urn** is DECLARED (`urn.official`): type lowercased, the composed
59
+ code (number + minor + prefix-glued parts), the v/ed version face
60
+ (the edition affix under the fold flag), the year-month date group —
61
+ byte-identical with the coded generator everywhere the coded one
62
+ renders at all. The CODED generator raises `ArgumentError` on months
63
+ 08/09 (`format('%02d', "08")` — the zero-padded string reads as an
64
+ invalid octal); pre-existing on main, pinned by the parity spec — the
65
+ compiled generator renders the captured month verbatim and does not
66
+ share the defect.
67
+ - **to_mr_string** is DECLARED (`mr:`): the coded mr_type /
68
+ mr_number_with_part / mr_year / mr_edition hooks over the columns,
69
+ byte-identical slugs (`etsi.eg.200-053.2004-06.1-5-1`), with the
70
+ wrapper's supplement suffix (`…_a1.1`) carried by the synthesized
71
+ classes.
69
72
 
70
73
  ## Non-obvious facts, learned the hard way
71
74
 
72
75
  - The generated `--ruby-out` lands at `lib/etsi_pfs.rb`; the autoload and
73
76
  the `#{__dir__}` paths require it at `lib/pubid/etsi_pfs.rb` — move both
74
77
  the file and the directory (commands above).
75
- - `core: ["number"]` in a `.pfs` urn type row would generate
76
- `core: [:number]`, which crashes `PubidUrn.define` at load time (the
77
- generator emits `core.map(&:to_sym)`); only `core: []` round-trips.
78
- The instance's URNs resolve through the CODED schema anyway (the flavor
79
- segment of the canonical tag is `etsi`), so the alias rows are the
80
- future-cutover shape.
78
+ - The instance's URNs resolve through the CODED schema anyway (the
79
+ flavor segment of the canonical tag is `etsi`), so the alias's schema
80
+ rows are the future-cutover shape; the wrapper rows now carry the
81
+ coded `core: [["number", :int]]` ordinals (the phase-3 int-kind
82
+ cores).
81
83
  - `V1..1` parses in BOTH flavors (`.` is a version_char) and `GS` is a
82
84
  real type token — neither is a parity negative.
83
85
  - `Pubid::EtsiPfs.parse("ETSI …")` never hijacks `Pubid.parse` (registry
84
86
  keys sort `"etsi"` first), and `Pubid.from_hash` of an alias hash
85
87
  resolves the coded `EtsiStandard` (one scheme, one `_type`).
86
88
  - The corpus inventory (`spec/fixtures/etsi/identifiers/full/`) is the
87
- parity source; its bucket split is pinned: plain 18,945 / parts 3,772 /
88
- edition 1,833 / supplement 168.
89
+ parity source; its former bucket split (plain 18,945 / parts 3,772 /
90
+ edition 1,833 / supplement 168) is now the per-gap parity trajectory —
91
+ all 24,718 rows agree on to_hash, to_pubid_urn, to_s, to_urn and
92
+ to_mr_string.
93
+ - The run's internal captures (`supplement_kind` / `supplement_number`)
94
+ bind only through the run's item rows, and unbound captures arrive as
95
+ raw `Parsanol::Slice` leaves — the generated builder unwraps
96
+ `Slice#content` (the cen_cenelec precedent).
89
97
 
90
- ## v1 boundaries
98
+ ## v1 boundaries (still)
91
99
 
92
- Single-type schemes only, `kind: string` fields only, no free-standing
93
- `stages:`/`supplements:` sections, no semantic decode rules — see
94
- `pubid-grammar`'s `definitions/README.md` for the language reference and
95
- the fail-closed validation rules.
100
+ Single declared type per scheme (the `supplements:` wrappers are the one
101
+ multi-class shape), no free-standing `stages:` maps, no semantic decode
102
+ rules — see `pubid-grammar`'s `definitions/README.md` for the language
103
+ reference and the fail-closed validation rules.
@@ -1,25 +1,58 @@
1
- module Pubid
2
- module EtsiPfs
3
- class Builder
4
- def self.build(parsed_data)
5
- new.build(parsed_data)
6
- end
1
+ module Pubid
2
+ module EtsiPfs
3
+ class Builder
4
+ # The wrapper classes by kind face: the run's dispatch.
5
+ SUPPLEMENT_WRAPPERS = {
6
+ "A" => Identifiers::Amendment,
7
+ "C" => Identifiers::Corrigendum,
8
+ }.freeze
9
+
10
+ def self.build(parsed_data)
11
+ new.build(parsed_data)
12
+ end
7
13
 
8
- def build(data)
9
- Identifiers::Standard.new(
14
+ def build(data)
15
+ identifier = Identifiers::Standard.new(
10
16
  type: data[:type],
11
17
  number: data[:number],
12
18
  minor: data[:minor],
13
- parts: data[:parts],
14
- supplements: data[:supplements],
15
- version: data[:version],
16
- edition: data[:edition],
19
+ parts: build_parts(data),
20
+ version: data[:version] || data[:edition],
21
+ is_edition: !data[:edition].nil?,
17
22
  year: data[:year],
18
23
  month: data[:month],
24
+ )
25
+ run = Array(data[:supplements])
26
+ run.each do |row|
27
+ item = row[:supplement] || row["supplement"] || {}
28
+ kind = unwrap(item[:supplement_kind] || item["supplement_kind"])
29
+ identifier = SUPPLEMENT_WRAPPERS.fetch(kind).new(
30
+ number: unwrap(item[:supplement_number] || item["supplement_number"]).to_i,
31
+ base: identifier,
19
32
  )
20
33
  end
34
+ identifier
35
+ end
36
+
37
+ # The run's internal captures bind only through the item row
38
+ # (the cen_cenelec precedent), so unbound ones arrive as raw
39
+ # leaves — a Parsanol::Slice or a {value:, offset: ...} span.
40
+ # Unwrap the captured value.
41
+ def unwrap(capture)
42
+ return capture.content if capture.is_a?(Parsanol::Slice)
43
+ return capture[:value] if capture.is_a?(Hash) && capture.key?(:value)
44
+
45
+ capture
46
+ end
47
+
48
+ # The parts column from its bound item rows.
49
+ def build_parts(data)
50
+ Array(data[:parts]).map do |row|
51
+ row[:part] || row["part"]
52
+ end.compact
21
53
  end
22
54
  end
23
55
  end
24
56
 
25
57
  Pubid::EtsiPfs::Builder.prepend(Pubid::Builder::AllPartsWrap)
58
+ end
@@ -1,50 +1,32 @@
1
- module Pubid
2
- module EtsiPfs
3
- class Identifier < ::Pubid::Identifier
4
- attribute :type, :string
5
- attribute :number, :string
6
- attribute :minor, :string
7
- attribute :parts, :string
8
- attribute :supplements, :string
9
- attribute :version, :string
10
- attribute :edition, :string
11
- attribute :year, :string
12
- attribute :month, :string
1
+ module Pubid
2
+ module EtsiPfs
3
+ class Identifier < ::Pubid::Identifier
4
+ attribute :publisher, :string, default: -> { "ETSI" }
13
5
 
14
- ETSI_TYPE_MAP = {
15
- "pubid:etsi:etsi-standard" => "Pubid::EtsiPfs::Identifiers::Standard",
16
- }.freeze
6
+ PUBLISHER = "ETSI"
17
7
 
18
- key_value do
19
- map "_type", to: :_type, polymorphic_map: ETSI_TYPE_MAP
20
- map "type", to: :type
21
- map "number", to: :number
22
- map "minor", to: :minor
23
- map "parts", to: :parts
24
- map "supplements", to: :supplements
25
- map "version", to: :version
26
- map "edition", to: :edition
27
- map "year", to: :year
28
- map "month", to: :month
29
- end
30
-
31
- PUBLISHER = "ETSI"
8
+ # The scheme's declared publisher token: the internal-form
9
+ # cut (to_s(with_publisher: false)) strips it from the
10
+ # rendered face.
11
+ def publisher_prefixes
12
+ [PUBLISHER]
13
+ end
32
14
 
33
- def self.parse(identifier)
34
- unless identifier.is_a?(String)
35
- raise Pubid::Errors::InvalidInputError,
36
- Pubid::INPUT_NOT_A_STRING_MESSAGE
37
- end
15
+ def self.parse(identifier)
16
+ unless identifier.is_a?(String)
17
+ raise Pubid::Errors::InvalidInputError,
18
+ Pubid::INPUT_NOT_A_STRING_MESSAGE
19
+ end
38
20
 
39
- if identifier.length > Pubid::MAX_INPUT_LENGTH
40
- raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
41
- end
21
+ if identifier.length > Pubid::MAX_INPUT_LENGTH
22
+ raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
23
+ end
42
24
 
43
- identifier = ::Pubid.utf8_input(identifier)
25
+ identifier = ::Pubid.utf8_input(identifier)
44
26
 
45
- parsed = Pubid::Parg::Model.bound(:etsi_pfs, identifier)
46
- Builder.build(parsed)
47
- end
27
+ parsed = Pubid::Parg::Model.bound(:etsi_pfs, identifier)
28
+ Builder.build(parsed)
48
29
  end
49
30
  end
50
31
  end
32
+ end
@@ -0,0 +1,20 @@
1
+ module Pubid
2
+ module EtsiPfs
3
+ module Identifiers
4
+ # The synthesized Amendment wrapper (PFS supplements
5
+ # declaration): the kind face "A" is the run's
6
+ # dispatch token and the printed notation's face.
7
+ class Amendment < Supplement
8
+ FACE = "A".freeze
9
+
10
+ def self.face
11
+ FACE
12
+ end
13
+
14
+ def self.polymorphic_name
15
+ "pubid:etsi:amendment"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module Pubid
2
+ module EtsiPfs
3
+ module Identifiers
4
+ # The synthesized Corrigendum wrapper (PFS supplements
5
+ # declaration): the kind face "C" is the run's
6
+ # dispatch token and the printed notation's face.
7
+ class Corrigendum < Supplement
8
+ FACE = "C".freeze
9
+
10
+ def self.face
11
+ FACE
12
+ end
13
+
14
+ def self.polymorphic_name
15
+ "pubid:etsi:corrigendum"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,22 +1,71 @@
1
- module Pubid
2
- module EtsiPfs
3
- module Identifiers
4
- # Concrete ETSI Standard identifier type (PFS type `standard`).
5
- class Standard < Identifier
1
+ module Pubid
2
+ module EtsiPfs
3
+ module Identifiers
4
+ # Concrete ETSI Standard identifier type (PFS type `standard`).
5
+ class Standard < Identifier
6
6
  TYPED_STAGES = [].freeze
7
7
 
8
- def self.type
9
- { key: :etsi, web: :etsi, title: "ETSI Standard", short: "ETSI" }
10
- end
8
+ attribute :type, :string
9
+ attribute :number, :string
10
+ attribute :minor, :string
11
+ attribute :parts, :string, collection: true, initialize_empty: true
12
+ attribute :version, :string
13
+ attribute :year, :string
14
+ attribute :month, :string
15
+ attribute :is_edition, :boolean, default: -> { false }
11
16
 
12
- # The scheme-canonical wire tag declared in the definition:
13
- # to_hash stays byte-identical with the scheme's coded
14
- # flavor, and this flavor's own from_hash routes the tag
15
- # to this class.
16
- def self.polymorphic_name
17
- "pubid:etsi:etsi-standard"
18
- end
17
+ key_value do
18
+ map "_type", to: :_type
19
+ map "type", to: :type
20
+ map "number", to: :number
21
+ map "minor", to: :minor
22
+ map "parts", to: :parts
23
+ map "version", to: :version
24
+ map "year", to: :year
25
+ map "month", to: :month
26
+ map "is_edition", to: :is_edition
27
+ end
28
+
29
+ def self.type
30
+ { key: :etsi, web: :etsi, title: "ETSI Standard", short: "ETSI" }
31
+ end
32
+
33
+ # The scheme-canonical wire tag declared in the definition:
34
+ # to_hash stays byte-identical with the scheme's coded
35
+ # flavor, and this flavor's own from_hash routes the tag
36
+ # to this class.
37
+ def self.polymorphic_name
38
+ "pubid:etsi:etsi-standard"
39
+ end
40
+
41
+ def mr_type
42
+ mr_sanitize(type)
43
+ end
44
+
45
+ def mr_number_with_part
46
+ mr_sanitize([number, minor, *Array(parts)].compact.join("-"))
47
+ end
48
+
49
+ def mr_year
50
+ mr_sanitize([year, month].compact.join("-"))
51
+ end
52
+
53
+ def mr_edition
54
+ mr_sanitize(version)
55
+ end
56
+
57
+ # The MR charset rule (the coded flavors' mr_sanitize):
58
+ # lowercase, [a-z0-9-], non-charset runs collapsed to one
59
+ # dash; empty answers nil so the joined slug never gains a
60
+ # blank segment.
61
+ def mr_sanitize(value)
62
+ return nil if value.nil? || value.to_s.empty?
63
+
64
+ sanitized = value.to_s.downcase
65
+ .gsub(/[^a-z0-9-]+/, "-").gsub(/\A-+|-+\z/, "")
66
+ sanitized.empty? ? nil : sanitized
19
67
  end
20
68
  end
21
69
  end
22
70
  end
71
+ end
@@ -0,0 +1,72 @@
1
+ module Pubid
2
+ module EtsiPfs
3
+ module Identifiers
4
+ # The supplement wrapper base (pubid#706 phase 3): the
5
+ # wrapper owns its ordinal and the base identifier; every
6
+ # delegated attribute reads through.
7
+ class Supplement < ::Pubid::EtsiPfs::Identifier
8
+ attribute :base, ::Pubid::EtsiPfs::Identifier
9
+ attribute :number, :integer
10
+
11
+ key_value do
12
+ map "_type", to: :_type
13
+ map "number", to: :number
14
+ map "base", with: { to: :base_to_kv, from: :base_from_kv }
15
+ end
16
+
17
+ # Serialize the base via its own to_hash so it collapses
18
+ # to the leaf standard shape (the coded converters'
19
+ # rule).
20
+ def base_to_kv(model, doc)
21
+ return unless model.base
22
+
23
+ doc.add_child(
24
+ Lutaml::KeyValue::DataModel::Element.new("base", model.base.to_hash),
25
+ )
26
+ end
27
+
28
+ # Re-dispatch through the flavor base so _type resolves
29
+ # to the concrete Standard (the coded JCGM/ETSI rule).
30
+ def base_from_kv(model, value)
31
+ return unless value
32
+
33
+ model.base = ::Pubid::EtsiPfs::Identifier.from_hash(value)
34
+ end
35
+
36
+ # Delegations: the wrapper reads through to the wrapped
37
+ # standard (the coded SupplementIdentifier shape).
38
+ def type
39
+ base&.type
40
+ end
41
+
42
+ def version
43
+ base&.version
44
+ end
45
+
46
+ def year
47
+ base&.year
48
+ end
49
+
50
+ def month
51
+ base&.month
52
+ end
53
+
54
+ def is_edition
55
+ base&.is_edition
56
+ end
57
+
58
+ # The printed notation: the kind face + the ordinal.
59
+ def supplement_notation
60
+ "#{self.class.face}#{number}"
61
+ end
62
+
63
+ # The MR slug's supplement suffix: the shared MrString
64
+ # recurses into base and appends this (the coded
65
+ # mr_supplement_suffix rule).
66
+ def mr_supplement_suffix
67
+ [supplement_notation.to_s.downcase, number].compact.join(".")
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,7 +1,10 @@
1
1
  module Pubid
2
2
  module EtsiPfs
3
3
  module Identifiers
4
- autoload :Standard, "#{__dir__}/identifiers/standard"
4
+ autoload :Standard, "#{__dir__}/identifiers/standard"
5
+ autoload :Supplement, "#{__dir__}/identifiers/supplement"
6
+ autoload :Amendment, "#{__dir__}/identifiers/amendment"
7
+ autoload :Corrigendum, "#{__dir__}/identifiers/corrigendum"
5
8
  end
6
9
  end
7
10
  end
@@ -12,8 +12,10 @@
12
12
  atom "year", "year"
13
13
  atom "month", "month"
14
14
  atom "version", "v"
15
- type "pubid:etsi_pfs:identifier", "id", core: []
16
- type "pubid:etsi_pfs:standard", "es", core: []
15
+ type "pubid:etsi:etsi-standard", "es", core: []
16
+ type "pubid:etsi:amendment", "amd", core: [["number", :int]]
17
+ type "pubid:etsi:corrigendum", "cor", core: [["number", :int]]
18
+ type "pubid:etsi:identifier", "id", core: []
17
19
  end
18
20
  end
19
21
  end
@@ -3,7 +3,35 @@ module Pubid
3
3
  # Human renderer, generated from the canonical template.
4
4
  class Renderer < ::Pubid::Renderers::Base
5
5
  def render(**_opts)
6
- "ETSI #{@id.type} #{@id.number}"
6
+ f = render_fields(@id)
7
+ "ETSI #{f[:type]} #{f[:number]}#{(" #{f[:minor]}") if f[:minor]}#{("#{f[:parts]}") if f[:parts]}#{f[:supplements]}#{if f[:version]; " V#{f[:version]}" elsif f[:edition]; " ed.#{f[:edition]}"; end}#{(" (#{f[:year]}-#{f[:month]})") if f[:year] && f[:month]}"
8
+ end
9
+
10
+ private
11
+
12
+ # The field view: a supplement wrapper reads through to
13
+ # the wrapped standard and carries the notation run
14
+ # ("/A1/C1", outermost last); the fold's target renders
15
+ # only outside the fold flag (the V/ed faces are mutually
16
+ # exclusive by grammar).
17
+ def render_fields(id)
18
+ root = id
19
+ run = +""
20
+ while root.is_a?(Identifiers::Supplement)
21
+ run.prepend("/#{root.supplement_notation}")
22
+ root = root.base
23
+ end
24
+ {
25
+ type: root.type,
26
+ number: root.number,
27
+ minor: root.minor,
28
+ parts: root.parts.any? ? root.parts.map { |item| "-#{item}" }.join : nil,
29
+ version: root.is_edition ? nil : root.version,
30
+ edition: root.is_edition ? root.version : nil,
31
+ year: root.year,
32
+ month: root.month,
33
+ supplements: run.empty? ? nil : run,
34
+ }
7
35
  end
8
36
  end
9
37
  end
@@ -1,12 +1,49 @@
1
1
  module Pubid
2
2
  module EtsiPfs
3
- # The official URN generator, pinned to the scheme's declared
4
- # namespace ("etsi") so to_urn stays
5
- # scheme-canonical under the compiled flavor key.
3
+ # The official URN generator: the scheme's declared namespace
4
+ # ("etsi") and the ordered segments the
5
+ # definition's urn.official declares — an absent field leaves no
6
+ # segment, a date group joins its fields with "-", a compose
7
+ # slots its fields with the declared glue, and a fold target
8
+ # switches to its edition affix while the flag holds.
6
9
  class UrnGenerator < ::Pubid::UrnGenerator::Base
7
10
  def urn_namespace
8
11
  "etsi"
9
12
  end
13
+
14
+ def generate
15
+ parts = ["urn", urn_namespace]
16
+ parts.concat(official_segments)
17
+ parts.join(":")
18
+ end
19
+
20
+ private
21
+
22
+ # A supplement wrapper reads through to the wrapped
23
+ # standard (the coded generator's own delegation shape).
24
+ def subject
25
+ current = @identifier
26
+ current = current.base while current.is_a?(Identifiers::Supplement)
27
+ current
28
+ end
29
+
30
+ def official_segments
31
+ segments = []
32
+ segments << subject.type.to_s.downcase if subject.type
33
+ code = []
34
+ code << subject.number.to_s if subject.number
35
+ code << " " + subject.minor.to_s if subject.minor
36
+ subject.parts.each { |item| code << "-" + item } if subject.parts.any?
37
+ segments << code.join unless code.empty?
38
+ if subject.is_edition
39
+ segments << "ed." + subject.version.to_s.downcase
40
+ elsif subject.version
41
+ segments << "v" + subject.version.to_s.downcase
42
+ end
43
+ date = [subject.year, subject.month].compact
44
+ segments << date.join("-") unless date.empty?
45
+ segments
46
+ end
10
47
  end
11
48
  end
12
49
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  #
3
3
  # PFS-generated flavor — DO NOT EDIT BY HAND.
4
- # Source: definitions/etsi.pfs (pfs:1) sha256:9343088bf087b55ea790057e6be0bbebe2272407ee81f035b52e5f9a4d5c7b76
4
+ # Source: definitions/etsi.pfs (pfs:1) sha256:490ba9435f44dbd2d59429ae6d9527909c39444f19fec5a33a8cad91e89e3581
5
5
  # Compiler: pubid/grammar/pfs 0.1.0 (baked with parsanol 1.3.126)
6
6
  # Regenerate: ruby scripts/pfs compile definitions/etsi.pfs --as etsi_pfs --ruby-out <dir>
7
7
 
@@ -16,6 +16,7 @@ module Pubid
16
16
  extend Pubid::PrefixesSupport
17
17
 
18
18
  PREFIXES = ["ETSI"].freeze
19
+
19
20
 
20
21
  autoload :Builder, "#{__dir__}/etsi_pfs/builder"
21
22
  autoload :Identifier, "#{__dir__}/etsi_pfs/identifier"
@@ -29,6 +29,41 @@
29
29
  `UrnParser` does not prepend `JIS ` to a code that starts with `WES `.
30
30
  - `PREFIXES` includes `"WES"`, so `Pubid.parse("WES …")` routes to JIS.
31
31
 
32
+ ## WES `(E)` before the year, `WES TR`, `WES TS` (relaton-jis#18)
33
+
34
+ - JSA Webdesk lists 14 WES ids that the grammar does not read:
35
+ `WES 1112(E):2022`, `WES TR 2032:2024`, `WES TS 2816(E):2015`.
36
+ - **A Ruby shim reads them, not the grammar.** `Parser.ingest` cuts
37
+ the `TR`/`TS` token and a `(E)`/`(J)` that comes before `:year`, and
38
+ binds them as `wes_type` and `wes_language`. Other input passes unchanged.
39
+ The hand-off `pubid__pubid-grammar__jis-wes-language-tr-ts` asks
40
+ pubid-grammar to bind the same two captures. After that lands, re-vendor
41
+ `jis.json`, re-pin `parg_reaffirmed_spec.rb`, and delete the shim. The
42
+ builder does not change.
43
+ - **`WES TR`/`WES TS` are classes**: `WeldingEngineeringTechnicalReport`
44
+ and `…TechnicalSpecification` < `WeldingEngineeringStandard`, with
45
+ `WES_TYPE` in `code` (`WES TR 2032`) and in `mr_type`. Class-strict `==`
46
+ keeps them apart from `WES 2032` and `JIS TR Z 2032`.
47
+ - **`language_before_year`** records where the source put the language. It
48
+ is spelling only (`equality_ignored`, a `langfirst` facet in the pubid
49
+ URN): `WES 1112(E):2022 == WES 1112:2022(E)`, and each renders as written.
50
+ The language itself is identity, so the `(E)` edition is not the Japanese
51
+ one. JIS ids never set the flag; `JIS X 0208(E):1997` still fails.
52
+ - The pubid URN schema now places `language` and the supplement ordinal
53
+ (`core: [["number", :int]]`; an `EXPL` number is the atom `n`). Before,
54
+ every `(E)` id and every supplement raised in `to_pubid_urn`, and no
55
+ supplement was in the fixture corpus to show it.
56
+ - **`Builder` reads both parse shapes.** The typed bindings
57
+ (`Parg::Model.bound`) carry a supplement flat (`amd_type`, …); the raw
58
+ tree (`Parg::Backend.parse`) nests it under `:amendment`/`:explanation`/
59
+ `:corrigendum`. `flatten_supplement` folds the nested form. Before, the
60
+ tree path dropped every supplement silently (`…/AMD 1:2000` built as the
61
+ bare base). `typed_binding_parity_spec.rb` found it when the first
62
+ supplement entered the pass corpus.
63
+ - **The shim lives in `Parser.ingest`** and returns `[face, bindings]`.
64
+ `typed_binding_parity_spec.rb` calls it on the tree path too and merges
65
+ the bindings, as `Identifier.parse` does.
66
+
32
67
  ## Equality includes the class
33
68
 
34
69
  - `Identifier#==` requires `other.instance_of?(self.class)`: `JIS TR X
@@ -8,6 +8,7 @@ module Pubid
8
8
  end
9
9
 
10
10
  def build(data)
11
+ data = flatten_supplement(data)
11
12
  # Handle supplement case first; the SYMBOL clause (if any) attaches to
12
13
  # the outermost identifier, be it the base or the supplement. The
13
14
  # bindings bind the supplement's fields flat (amd_type/amd_number/…),
@@ -29,6 +30,19 @@ module Pubid
29
30
 
30
31
  private
31
32
 
33
+ SUPPLEMENT_KEYS = %i[amendment explanation corrigendum].freeze
34
+
35
+ # The typed bindings carry a supplement's fields flat (amd_type, …).
36
+ # The raw parse tree nests them under :amendment, :explanation or
37
+ # :corrigendum. Read both shapes, or the tree path drops the
38
+ # supplement.
39
+ def flatten_supplement(data)
40
+ key = SUPPLEMENT_KEYS.find { |k| data[k].is_a?(Hash) }
41
+ return data unless key
42
+
43
+ data.except(key).merge(data[key].compact)
44
+ end
45
+
32
46
  # nil => no SYMBOL clause; "" => bare "SYMBOL" keyword; otherwise value.
33
47
  def attach_symbol(identifier, data)
34
48
  identifier.symbol = data[:symbol_value].to_s if data[:symbol_present]
@@ -44,13 +58,30 @@ module Pubid
44
58
  reaffirmed: (true if data[:reaffirmed]),
45
59
  }
46
60
  # A WES id carries no series letter, so its series stays nil.
47
- wes = Identifiers::WeldingEngineeringStandard
48
- return wes.new(**attrs) if data[:wes]
61
+ return build_wes(data, attrs) if data[:wes]
49
62
 
50
63
  jis_class_for_type(data[:type]&.to_s)
51
64
  .new(series: data[:series].to_s, **attrs)
52
65
  end
53
66
 
67
+ # `wes_type` ("TR"/"TS") picks the WES class. `wes_language` is a
68
+ # language printed before the year ("WES 1112(E):2022").
69
+ def build_wes(data, attrs)
70
+ attrs = attrs.merge(wes_language_attrs(data)) if data[:wes_language]
71
+ Identifiers::WeldingEngineeringStandard
72
+ .class_for_type(data[:wes_type]&.to_s).new(**attrs)
73
+ end
74
+
75
+ def wes_language_attrs(data)
76
+ if data[:language]
77
+ raise Pubid::Errors::ParseError.new(
78
+ "a WES id carries one language, not two", nil
79
+ )
80
+ end
81
+
82
+ { language: data[:wes_language].to_s, language_before_year: true }
83
+ end
84
+
54
85
  def jis_class_for_type(type)
55
86
  case type
56
87
  when "TR" then Identifiers::TechnicalReport
@@ -21,6 +21,11 @@ module Pubid
21
21
  attribute :parts, :string, collection: true # Optional multi-level parts
22
22
  attribute :year, :integer
23
23
  attribute :language, :string # "E" or "J"
24
+ # A WES id can print its language before the year ("WES 1112(E):2022")
25
+ # or after it ("WES 1112:2022(E)"). This records the source position.
26
+ # It is spelling only, so `==` does not read it.
27
+ attribute :language_before_year, :boolean
28
+ equality_ignored :language_before_year
24
29
  # Reaffirmation (再確認): a trailing "R" on the year marks an edition
25
30
  # that was reaffirmed without revision (e.g. ":2019R").
26
31
  attribute :reaffirmed, :boolean
@@ -45,6 +50,10 @@ module Pubid
45
50
  "pubid:jis:explanation" => "Pubid::Jis::Identifiers::Explanation",
46
51
  "pubid:jis:welding-engineering-standard" =>
47
52
  "Pubid::Jis::Identifiers::WeldingEngineeringStandard",
53
+ "pubid:jis:welding-engineering-technical-report" =>
54
+ "Pubid::Jis::Identifiers::WeldingEngineeringTechnicalReport",
55
+ "pubid:jis:welding-engineering-technical-specification" =>
56
+ "Pubid::Jis::Identifiers::WeldingEngineeringTechnicalSpecification",
48
57
  }.freeze
49
58
 
50
59
  key_value do
@@ -54,6 +63,7 @@ module Pubid
54
63
  map "parts", to: :parts
55
64
  map "year", to: :year
56
65
  map "language", to: :language
66
+ map "language_before_year", to: :language_before_year
57
67
  map "reaffirmed", to: :reaffirmed
58
68
  # render_empty keeps a bare "SYMBOL" (empty-string value) in the hash so
59
69
  # it round-trips distinctly from "no symbol" (nil).
@@ -75,6 +85,17 @@ module Pubid
75
85
  reaffirmed == true
76
86
  end
77
87
 
88
+ def language_before_year?
89
+ language_before_year == true
90
+ end
91
+
92
+ # The ":year[R]" and "(lang)" tail, in the order the source used.
93
+ def year_and_language
94
+ year_part = year ? ":#{year_with_reaffirmation}" : ""
95
+ lang_part = language ? "(#{language})" : ""
96
+ language_before_year? ? lang_part + year_part : year_part + lang_part
97
+ end
98
+
78
99
  # Render a year with its reaffirmation marker, e.g. "2019R".
79
100
  def year_with_reaffirmation
80
101
  "#{year}#{'R' if reaffirmed?}"
@@ -184,10 +205,9 @@ module Pubid
184
205
  raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
185
206
  end
186
207
 
187
- identifier = ::Pubid.utf8_input(identifier)
188
-
189
- parsed = Pubid::Parg::Model.bound(:jis, identifier)
190
- Builder.build(parsed)
208
+ face, bindings = Parser.ingest(::Pubid.utf8_input(identifier))
209
+ parsed = Pubid::Parg::Model.bound(:jis, face)
210
+ Builder.build(parsed.merge(bindings))
191
211
  end
192
212
  end
193
213
  end
@@ -6,9 +6,20 @@ module Pubid
6
6
  # Japan Welding Engineering Society standard. JSA sells WES next to JIS,
7
7
  # so it parses through the JIS flavor, but the id carries no series
8
8
  # letter and no JIS prefix.
9
- # Examples: WES 7700-1:2019, WES 7301:2020R
9
+ # Examples: WES 7700-1:2019, WES 7301:2020R, WES 1112(E):2022
10
10
  class WeldingEngineeringStandard < SingleIdentifier
11
11
  PUBLISHER = "WES"
12
+ # The type token after the WES root: "TR", "TS", or nil.
13
+ WES_TYPE = nil
14
+
15
+ # The WES class for a type token ("TR", "TS" or nil).
16
+ def self.class_for_type(type)
17
+ case type
18
+ when "TR" then WeldingEngineeringTechnicalReport
19
+ when "TS" then WeldingEngineeringTechnicalSpecification
20
+ else WeldingEngineeringStandard
21
+ end
22
+ end
12
23
 
13
24
  def type_prefix
14
25
  nil
@@ -20,8 +31,12 @@ module Pubid
20
31
  nil
21
32
  end
22
33
 
34
+ def wes_type
35
+ self.class::WES_TYPE
36
+ end
37
+
23
38
  def code
24
- result = "#{PUBLISHER} #{number}"
39
+ result = [PUBLISHER, wes_type, number].compact.join(" ")
25
40
  result += parts.map { |p| "-#{p}" }.join if parts&.any?
26
41
  result
27
42
  end
@@ -29,6 +44,10 @@ module Pubid
29
44
  def mr_publisher
30
45
  PUBLISHER.downcase
31
46
  end
47
+
48
+ def mr_type
49
+ wes_type&.downcase
50
+ end
32
51
  end
33
52
  end
34
53
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Jis
5
+ module Identifiers
6
+ # WES technical report. The type token follows the WES root.
7
+ # Example: WES TR 2032:2024
8
+ class WeldingEngineeringTechnicalReport < WeldingEngineeringStandard
9
+ WES_TYPE = "TR"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pubid
4
+ module Jis
5
+ module Identifiers
6
+ # WES technical specification. The type token follows the WES root.
7
+ # Examples: WES TS 2816:2015, WES TS 2816(E):2015
8
+ class WeldingEngineeringTechnicalSpecification <
9
+ WeldingEngineeringStandard
10
+ WES_TYPE = "TS"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -15,6 +15,11 @@ module Pubid
15
15
  "#{__dir__}/identifiers/technical_specification"
16
16
  autoload :WeldingEngineeringStandard,
17
17
  "#{__dir__}/identifiers/welding_engineering_standard"
18
+ autoload :WeldingEngineeringTechnicalReport,
19
+ "#{__dir__}/identifiers/welding_engineering_technical_report"
20
+ autoload :WeldingEngineeringTechnicalSpecification,
21
+ "#{__dir__}/identifiers/" \
22
+ "welding_engineering_technical_specification"
18
23
  end
19
24
  end
20
25
  end
@@ -2,7 +2,37 @@
2
2
 
3
3
  module Pubid
4
4
  module Jis
5
+ # The JIS grammar is the vendored artifact data/parg/jis.json. This class
6
+ # holds only the Ruby ingestion in front of it.
5
7
  class Parser
8
+ # A WES type token or a language before the year. Full-width forms
9
+ # are accepted, as in the grammar.
10
+ WES_SHIM = /
11
+ \A(?<root>WES[  ])
12
+ (?:(?<type>TR|TS)[  ])?
13
+ (?<number>\d+(?:[-ー]\d+)*)
14
+ (?:[((](?<lang>[EJ])[))](?=[::]))?
15
+ /x
16
+
17
+ # The vendored grammar (pubid-grammar artifacts/jis.json) reads neither
18
+ # "WES TR 2032" nor "WES 1112(E):2022". Until it does, cut those tokens
19
+ # from the input and bind them as `wes_type` and `wes_language`, the
20
+ # captures the grammar is to bind (hand-off
21
+ # pubid__pubid-grammar__jis-wes-language-tr-ts). Other input passes
22
+ # unchanged.
23
+ # @param input [String]
24
+ # @return [Array(String, Hash)] the input for the grammar, the bindings
25
+ # to merge into what the grammar binds
26
+ def self.ingest(input)
27
+ match = WES_SHIM.match(input)
28
+ return [input, {}] unless match && (match[:type] || match[:lang])
29
+
30
+ bindings = {}
31
+ bindings[:wes_type] = match[:type] if match[:type]
32
+ bindings[:wes_language] = match[:lang] if match[:lang]
33
+ rest = input[match.end(0)..]
34
+ ["#{match[:root]}#{match[:number]}#{rest}", bindings]
35
+ end
6
36
  end
7
37
  end
8
38
  end
@@ -13,17 +13,28 @@ module Pubid
13
13
  atom "parts", "part", kind: :list
14
14
  atom "year", "year", kind: :int
15
15
  atom "reaffirmed", "reaffirmed", kind: :bool
16
+ atom "language", "lang"
17
+ # Where a WES id prints its language: "WES 1112(E):2022" against
18
+ # "WES 1112:2022(E)". Spelling only.
19
+ facet "language_before_year", "langfirst", kind: :bool
16
20
 
17
21
  type "pubid:jis:all-parts", "all"
18
- type "pubid:jis:amendment", "amd"
19
- type "pubid:jis:corrigendum", "cor"
20
- type "pubid:jis:explanation", "e"
22
+ # A supplement's number is its Integer ordinal. The document it
23
+ # supplements comes first, along +base+. An explanation can have no
24
+ # number ("…/EXPL"), so its number is an atom.
25
+ supplement = [["number", :int]]
26
+ type "pubid:jis:amendment", "amd", core: supplement
27
+ type "pubid:jis:corrigendum", "cor", core: supplement
28
+ type "pubid:jis:explanation", "e", core: [],
29
+ atoms: [["number", "n", :int]]
21
30
  type "pubid:jis:identifier", "id"
22
31
  type "pubid:jis:japanese-industrial-standard", "jis"
23
32
  type "pubid:jis:standard", "std"
24
33
  type "pubid:jis:technical-report", "tr"
25
34
  type "pubid:jis:technical-specification", "ts"
26
35
  type "pubid:jis:welding-engineering-standard", "wes"
36
+ type "pubid:jis:welding-engineering-technical-report", "westr"
37
+ type "pubid:jis:welding-engineering-technical-specification", "wests"
27
38
  end
28
39
  end
29
40
  end
@@ -49,8 +49,7 @@ module Pubid
49
49
  result = parts.join(" ")
50
50
  result += " " if result.length.positive?
51
51
  result += id.code.to_s
52
- result += ":#{id.year_with_reaffirmation}" if id.year
53
- result += "(#{id.language})" if id.language
52
+ result += id.year_and_language
54
53
  result += id.symbol_suffix
55
54
  result
56
55
  end
data/lib/pubid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pubid
4
- VERSION = "2.0.24"
4
+ VERSION = "2.0.26"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pubid
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.24
4
+ version: 2.0.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -551,7 +551,10 @@ files:
551
551
  - lib/pubid/etsi_pfs/builder.rb
552
552
  - lib/pubid/etsi_pfs/identifier.rb
553
553
  - lib/pubid/etsi_pfs/identifiers.rb
554
+ - lib/pubid/etsi_pfs/identifiers/amendment.rb
555
+ - lib/pubid/etsi_pfs/identifiers/corrigendum.rb
554
556
  - lib/pubid/etsi_pfs/identifiers/standard.rb
557
+ - lib/pubid/etsi_pfs/identifiers/supplement.rb
555
558
  - lib/pubid/etsi_pfs/parser.rb
556
559
  - lib/pubid/etsi_pfs/pubid_urn_schema.rb
557
560
  - lib/pubid/etsi_pfs/renderer.rb
@@ -897,6 +900,8 @@ files:
897
900
  - lib/pubid/jis/identifiers/technical_report.rb
898
901
  - lib/pubid/jis/identifiers/technical_specification.rb
899
902
  - lib/pubid/jis/identifiers/welding_engineering_standard.rb
903
+ - lib/pubid/jis/identifiers/welding_engineering_technical_report.rb
904
+ - lib/pubid/jis/identifiers/welding_engineering_technical_specification.rb
900
905
  - lib/pubid/jis/parser.rb
901
906
  - lib/pubid/jis/pubid_urn_schema.rb
902
907
  - lib/pubid/jis/renderer.rb