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 +4 -4
- data/data/parg/etsi_pfs.json +1 -1
- data/lib/pubid/doi_pfs/builder.rb +2 -2
- data/lib/pubid/etsi_pfs/CLAUDE.md +48 -40
- data/lib/pubid/etsi_pfs/builder.rb +45 -12
- data/lib/pubid/etsi_pfs/identifier.rb +23 -41
- data/lib/pubid/etsi_pfs/identifiers/amendment.rb +20 -0
- data/lib/pubid/etsi_pfs/identifiers/corrigendum.rb +20 -0
- data/lib/pubid/etsi_pfs/identifiers/standard.rb +64 -15
- data/lib/pubid/etsi_pfs/identifiers/supplement.rb +72 -0
- data/lib/pubid/etsi_pfs/identifiers.rb +4 -1
- data/lib/pubid/etsi_pfs/pubid_urn_schema.rb +4 -2
- data/lib/pubid/etsi_pfs/renderer.rb +29 -1
- data/lib/pubid/etsi_pfs/urn_generator.rb +40 -3
- data/lib/pubid/etsi_pfs.rb +2 -1
- data/lib/pubid/jis/CLAUDE.md +35 -0
- data/lib/pubid/jis/builder.rb +33 -2
- data/lib/pubid/jis/identifier.rb +24 -4
- data/lib/pubid/jis/identifiers/welding_engineering_standard.rb +21 -2
- data/lib/pubid/jis/identifiers/welding_engineering_technical_report.rb +13 -0
- data/lib/pubid/jis/identifiers/welding_engineering_technical_specification.rb +14 -0
- data/lib/pubid/jis/identifiers.rb +5 -0
- data/lib/pubid/jis/parser.rb +30 -0
- data/lib/pubid/jis/pubid_urn_schema.rb +14 -3
- data/lib/pubid/jis/renderer.rb +1 -2
- data/lib/pubid/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef65c4cbf5afc5635f55300fc57894462ecc211d2dd51d2c2605dafda75a84a3
|
|
4
|
+
data.tar.gz: 89c5dccd912343c12ea157220089f7b1e0cf708abefe45e63c30c3bf712177e8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 135acc1bf9bd56785e0c861db78c0cad0b5026527ca6798ba1e5df7f9d2a19124211cc2b3583db2b332bcd7fc29300736b28c44b0dbbbdd482742607df2f2ddc
|
|
7
|
+
data.tar.gz: ef9dc843feed906ae25c564839c6d710c30e3e4a3d7fb1e96fc4376ca73551deaade688e3bf7ceb825fd93292962a4db722f7994b4036a73c61d99e91a4f5626
|
data/data/parg/etsi_pfs.json
CHANGED
|
@@ -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"}
|
|
@@ -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:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Amendment
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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
|
|
93
|
-
`stages
|
|
94
|
-
`pubid-grammar`'s `definitions/README.md` for the language
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
15
|
-
"pubid:etsi:etsi-standard" => "Pubid::EtsiPfs::Identifiers::Standard",
|
|
16
|
-
}.freeze
|
|
6
|
+
PUBLISHER = "ETSI"
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
21
|
+
if identifier.length > Pubid::MAX_INPUT_LENGTH
|
|
22
|
+
raise Pubid::Errors::InvalidInputError, Pubid::INPUT_TOO_LONG_MESSAGE
|
|
23
|
+
end
|
|
42
24
|
|
|
43
|
-
|
|
25
|
+
identifier = ::Pubid.utf8_input(identifier)
|
|
44
26
|
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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:
|
|
16
|
-
type "pubid:
|
|
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
|
-
|
|
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
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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
|
data/lib/pubid/etsi_pfs.rb
CHANGED
|
@@ -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:
|
|
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"
|
data/lib/pubid/jis/CLAUDE.md
CHANGED
|
@@ -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
|
data/lib/pubid/jis/builder.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
data/lib/pubid/jis/identifier.rb
CHANGED
|
@@ -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
|
-
|
|
188
|
-
|
|
189
|
-
parsed
|
|
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 =
|
|
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
|
data/lib/pubid/jis/parser.rb
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
data/lib/pubid/jis/renderer.rb
CHANGED
|
@@ -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 +=
|
|
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
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.
|
|
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
|