@pubid/pubid 0.1.4 → 0.1.6
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.
- package/README.md +22 -6
- package/artifact/corpus.json.gz +0 -0
- package/dist/conformance/implementation.d.ts +3 -0
- package/dist/flavors/bipm/index.js +39 -0
- package/dist/flavors/gb/index.d.ts +8 -1
- package/dist/flavors/gb/index.js +55 -7
- package/dist/flavors/iec/index.js +80 -0
- package/dist/flavors/ieee/grammar.js +77 -6
- package/dist/flavors/ieee/index.js +76 -4
- package/dist/flavors/ieee/model.d.ts +2 -0
- package/dist/flavors/ieee/model.js +101 -12
- package/dist/flavors/ieee/update_codes.js +10 -4
- package/dist/flavors/index.js +27 -0
- package/dist/flavors/jcgm/index.js +61 -0
- package/dist/flavors/nist/grammar.js +9 -2
- package/dist/flavors/nist/index.js +8 -4
- package/dist/flavors/nist/model.js +10 -3
- package/dist/flavors/oiml/grammar.js +29 -3
- package/dist/flavors/oiml/implementation.js +46 -1
- package/dist/flavors/oiml/model.d.ts +19 -0
- package/dist/flavors/oiml/model.js +133 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/model/identifier.d.ts +1 -0
- package/dist/model/identifier.js +1 -1
- package/dist/urn/parsers.d.ts +5 -0
- package/dist/urn/parsers.js +566 -0
- package/dist/urn/types.d.ts +12 -0
- package/dist/urn/types.js +8 -0
- package/package.json +1 -1
|
@@ -126,6 +126,8 @@ const DRAFT_MONTH_NAMES = {
|
|
|
126
126
|
export class IeeeDraft {
|
|
127
127
|
version;
|
|
128
128
|
revision;
|
|
129
|
+
isoStage;
|
|
130
|
+
isoIteration;
|
|
129
131
|
year;
|
|
130
132
|
month;
|
|
131
133
|
day;
|
|
@@ -137,6 +139,8 @@ export class IeeeDraft {
|
|
|
137
139
|
this.commaBeforeMonthSet = "comma_before_month" in attrs;
|
|
138
140
|
this.version = String(attrs["version"] ?? "");
|
|
139
141
|
this.revision = attrs["revision"];
|
|
142
|
+
this.isoStage = attrs["iso_stage"];
|
|
143
|
+
this.isoIteration = attrs["iso_iteration"];
|
|
140
144
|
this.year = attrs["year"];
|
|
141
145
|
this.originalMonth = attrs["month"];
|
|
142
146
|
this.month = attrs["month"] !== undefined
|
|
@@ -155,24 +159,45 @@ export class IeeeDraft {
|
|
|
155
159
|
body = body.slice(1);
|
|
156
160
|
else if (body.startsWith("D") && body.length > 1)
|
|
157
161
|
body = body.slice(1);
|
|
162
|
+
// The compound stage suffix (docs/IEEE-DRAFT-STAGES.md §1.3):
|
|
163
|
+
// "=DIS.3" is canonical - D (draft) = DIS (the stage), iteration 3.
|
|
164
|
+
// "=DDIS.3", "=DDIS3", "=DDIS-3" (the stage echoed with its own D)
|
|
165
|
+
// are accepted aliases. Captures [stage, iteration].
|
|
166
|
+
const compound = /=D?(PWI|NP|WD|CDV|FDIS|DIS|CD)(?:[.-]?(\d+))?$/;
|
|
167
|
+
let isoStage;
|
|
168
|
+
let isoIteration;
|
|
169
|
+
const cm = compound.exec(body);
|
|
170
|
+
if (cm !== null) {
|
|
171
|
+
isoStage = cm[1];
|
|
172
|
+
isoIteration = cm[2];
|
|
173
|
+
body = body.slice(0, cm.index);
|
|
174
|
+
}
|
|
158
175
|
const monthAlt = Object.keys(DRAFT_MONTH_NAMES)
|
|
159
176
|
.sort((a, b) => b.length - a.length)
|
|
160
177
|
.map((m) => `${m.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.?`)
|
|
161
178
|
.join("|");
|
|
162
179
|
const suffix = new RegExp(`(?:(, | )(${monthAlt})(?: (\\d{1,2}))?(?:, | )| )((?:19|20)\\d{2}[a-z]{0,2})$`);
|
|
163
180
|
const m = suffix.exec(body);
|
|
164
|
-
if (m === null)
|
|
165
|
-
return new IeeeDraft({ version: body });
|
|
181
|
+
if (m === null) {
|
|
182
|
+
return new IeeeDraft({ version: body, iso_stage: isoStage, iso_iteration: isoIteration });
|
|
183
|
+
}
|
|
166
184
|
return new IeeeDraft({
|
|
167
185
|
version: body.slice(0, m.index),
|
|
168
186
|
month: m[2],
|
|
169
187
|
day: m[3],
|
|
170
188
|
year: m[4],
|
|
171
189
|
comma_before_month: m[1] === ", ",
|
|
190
|
+
iso_stage: isoStage,
|
|
191
|
+
iso_iteration: isoIteration,
|
|
172
192
|
});
|
|
173
193
|
}
|
|
174
194
|
render() {
|
|
175
195
|
let result = `/D${this.version}`;
|
|
196
|
+
if (this.isoStage !== undefined)
|
|
197
|
+
result += `=${this.isoStage}`;
|
|
198
|
+
if (this.isoStage !== undefined && this.isoIteration !== undefined) {
|
|
199
|
+
result += `.${this.isoIteration}`;
|
|
200
|
+
}
|
|
176
201
|
if (this.revision !== undefined)
|
|
177
202
|
result += `.${this.revision}`;
|
|
178
203
|
if (this.year !== undefined) {
|
|
@@ -432,10 +457,14 @@ export function renderIeeeBase(id) {
|
|
|
432
457
|
const iecIeeOnly = id.publisher === "IEC" && JSON.stringify(id.copublisher) === '["IEEE"]';
|
|
433
458
|
if (codeObj !== undefined) {
|
|
434
459
|
let result = codeObj.render();
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (stageObj?.projectStatus === true && shouldRenderType
|
|
460
|
+
// Prepend P if this is a project AND code doesn't already have P.
|
|
461
|
+
// A recorded project marker (the source spelled the P on a
|
|
462
|
+
// non-IEEE-led publisher) prints regardless of the publisher.
|
|
463
|
+
if ((stageObj?.projectStatus === true && shouldRenderType ||
|
|
464
|
+
id.constructor.polymorphicName ===
|
|
465
|
+
"pubid:ieee:project-draft-identifier" &&
|
|
466
|
+
id["project_marker"] === true) &&
|
|
467
|
+
!result.startsWith("P")) {
|
|
439
468
|
result = `P${result}`;
|
|
440
469
|
}
|
|
441
470
|
if (id.year !== undefined && draftObj === undefined && id.edition === undefined && id.month === undefined) {
|
|
@@ -560,6 +589,12 @@ class IeeeUrnGenerator extends BaseUrnGenerator {
|
|
|
560
589
|
return SPECIAL_URN_OF(this.identifier);
|
|
561
590
|
}
|
|
562
591
|
publisherComponent() {
|
|
592
|
+
// The IEC/IEEE co-published pair is definitional to the class and
|
|
593
|
+
// was previously lost ("urn:ieee:ieee" — no number, no draft).
|
|
594
|
+
if (this.identifier.constructor.polymorphicName ===
|
|
595
|
+
"pubid:ieee:iec-ieee-copublished") {
|
|
596
|
+
return "iec-ieee";
|
|
597
|
+
}
|
|
563
598
|
// Supplements delegate #publisher/#copublisher to their base (Ruby
|
|
564
599
|
// SupplementIdentifier); DualPublished derives publisher from BOTH
|
|
565
600
|
// members; MultiNumbered derives it from its primary.
|
|
@@ -589,12 +624,35 @@ class IeeeUrnGenerator extends BaseUrnGenerator {
|
|
|
589
624
|
return pub;
|
|
590
625
|
}
|
|
591
626
|
typeComponent() {
|
|
627
|
+
// A joint stage draft carries the stage in its draft clause and the
|
|
628
|
+
// project P inside the code — a separate type segment would
|
|
629
|
+
// duplicate both.
|
|
630
|
+
const jointDraft = this.identifier["ieee_draft"];
|
|
631
|
+
if (jointDraft !== undefined && jointDraft.startsWith("D="))
|
|
632
|
+
return undefined;
|
|
592
633
|
const type = this.identifier.type;
|
|
593
634
|
if (type === undefined || type.trim() === "" || type === "Std")
|
|
594
635
|
return undefined;
|
|
595
636
|
return type.toLowerCase().replaceAll(" ", ".");
|
|
596
637
|
}
|
|
597
638
|
codeComponent() {
|
|
639
|
+
const id = this.identifier;
|
|
640
|
+
// The co-published code is number+parts+separators (no year — its
|
|
641
|
+
// own segment), e.g. "61886-1".
|
|
642
|
+
if (id.constructor.polymorphicName ===
|
|
643
|
+
"pubid:ieee:iec-ieee-copublished") {
|
|
644
|
+
const self = id;
|
|
645
|
+
const number = self["number"];
|
|
646
|
+
if (number === undefined || number === "")
|
|
647
|
+
return undefined;
|
|
648
|
+
const parts = self["parts"] ?? [];
|
|
649
|
+
const separators = self["separators"] ?? [];
|
|
650
|
+
let code = number;
|
|
651
|
+
separators.forEach((sep, i) => {
|
|
652
|
+
code += `${sep}${parts[i] ?? ""}`;
|
|
653
|
+
});
|
|
654
|
+
return code;
|
|
655
|
+
}
|
|
598
656
|
// Wrappers delegate code_obj to the identifier they wrap (Ruby
|
|
599
657
|
// AdoptedStandard/CsaDualPublished/MultiNumberedIdentifier).
|
|
600
658
|
const self = this.identifier;
|
|
@@ -615,6 +673,17 @@ class IeeeUrnGenerator extends BaseUrnGenerator {
|
|
|
615
673
|
return root === id ? undefined : root.yearForUrn();
|
|
616
674
|
}
|
|
617
675
|
draftComponent() {
|
|
676
|
+
// The joint stage draft's stage rides in its ieee_draft clause
|
|
677
|
+
// ("D=CDV[:2020]"), emitted directly as the draft segment.
|
|
678
|
+
const jointDraft = this.identifier["ieee_draft"];
|
|
679
|
+
if (jointDraft !== undefined && jointDraft.startsWith("D="))
|
|
680
|
+
return `draft.${jointDraft}`;
|
|
681
|
+
const draftInfo = this.identifier["draft_info"];
|
|
682
|
+
if (draftInfo !== undefined &&
|
|
683
|
+
this.identifier.constructor.polymorphicName ===
|
|
684
|
+
"pubid:ieee:iec-ieee-copublished") {
|
|
685
|
+
return draftInfo === "" ? undefined : `draft.${draftInfo}`;
|
|
686
|
+
}
|
|
618
687
|
const draftObj = this.identifier.draftObject();
|
|
619
688
|
return draftObj === undefined ? undefined : `draft.${draftObj.render()}`;
|
|
620
689
|
}
|
|
@@ -738,7 +807,13 @@ export const SupplementBaseDefs = {
|
|
|
738
807
|
base: { type: IeeeIdentifier },
|
|
739
808
|
};
|
|
740
809
|
export const StandardClass = ieeeClass({ kind: "standard" }, { codeColumns: true });
|
|
741
|
-
export const ProjectDraftIdentifierClass = ieeeClass({ kind: "project-draft-identifier" }, {
|
|
810
|
+
export const ProjectDraftIdentifierClass = ieeeClass({ kind: "project-draft-identifier" }, {
|
|
811
|
+
codeColumns: true,
|
|
812
|
+
extraDefs: {
|
|
813
|
+
project_marker: { type: "boolean", default: false },
|
|
814
|
+
},
|
|
815
|
+
extraMappings: keyValue({ wire: "project_marker", to: "project_marker" }),
|
|
816
|
+
});
|
|
742
817
|
export const SiStandardClass = ieeeClass({ kind: "si-standard" }, {
|
|
743
818
|
codeColumns: true,
|
|
744
819
|
specialUrn: "si",
|
|
@@ -940,8 +1015,9 @@ export const JointDevelopmentClass = ieeeClass({ kind: "joint-development" }, {
|
|
|
940
1015
|
lead_party: { type: "string" },
|
|
941
1016
|
iso_stage: { type: "string" },
|
|
942
1017
|
ieee_draft: { type: "string" },
|
|
1018
|
+
parenthetical_content: { type: "string" },
|
|
943
1019
|
},
|
|
944
|
-
extraMappings: keyValue({ wire: "publishers", to: "publishers" }, { wire: "lead_party", to: "lead_party" }, { wire: "iso_stage", to: "iso_stage" }, { wire: "ieee_draft", to: "ieee_draft" }),
|
|
1020
|
+
extraMappings: keyValue({ wire: "publishers", to: "publishers" }, { wire: "lead_party", to: "lead_party" }, { wire: "iso_stage", to: "iso_stage" }, { wire: "ieee_draft", to: "ieee_draft" }, { wire: "parenthetical_content", to: "parenthetical_content" }),
|
|
945
1021
|
wireDelete: { keys: ["publisher", "copublisher"] },
|
|
946
1022
|
render: (id) => {
|
|
947
1023
|
const publishers = id["publishers"];
|
|
@@ -951,6 +1027,9 @@ export const JointDevelopmentClass = ieeeClass({ kind: "joint-development" }, {
|
|
|
951
1027
|
const codeObj = id.codeObj;
|
|
952
1028
|
// ISO-led renders the ISO format ALWAYS (the stage-less published
|
|
953
1029
|
// form "ISO/IEC/IEEE 12207.2:2020" prints colon-year, no stage).
|
|
1030
|
+
// IEEE semantics: P = project (a draft); no P = a standard. The
|
|
1031
|
+
// P-state is identity-bearing and prints as spelled - it is never
|
|
1032
|
+
// added or stripped here.
|
|
954
1033
|
if (leadParty === "ISO") {
|
|
955
1034
|
const parts = [];
|
|
956
1035
|
if (publishers !== undefined && publishers.length > 0)
|
|
@@ -961,21 +1040,31 @@ export const JointDevelopmentClass = ieeeClass({ kind: "joint-development" }, {
|
|
|
961
1040
|
else if (isoStage !== undefined) {
|
|
962
1041
|
parts.push(isoStage);
|
|
963
1042
|
}
|
|
964
|
-
|
|
1043
|
+
const codeStr = codeObj?.render() ?? "";
|
|
965
1044
|
if (codeStr !== "")
|
|
966
1045
|
parts.push(codeStr);
|
|
967
1046
|
let result = parts.join(" ");
|
|
968
1047
|
if (id.year !== undefined)
|
|
969
1048
|
result += `:${id.year}`;
|
|
1049
|
+
// The joint stage-draft clause ("D=WD.5") rides after the year in
|
|
1050
|
+
// the ISO-led print; only the bare language/edition marker ("(E)")
|
|
1051
|
+
// prints - a relationship narrative is metadata, not identity.
|
|
1052
|
+
const jointDraft = id["ieee_draft"];
|
|
1053
|
+
if (jointDraft !== undefined && jointDraft.startsWith("D="))
|
|
1054
|
+
result += `/${jointDraft}`;
|
|
1055
|
+
const marker = id["parenthetical_content"];
|
|
1056
|
+
if (marker !== undefined && /^[A-Z](?:\s*[/&]\s*[A-Z])*$/.test(marker)) {
|
|
1057
|
+
result += ` (${marker})`;
|
|
1058
|
+
}
|
|
970
1059
|
return result;
|
|
971
1060
|
}
|
|
972
1061
|
// IEEE format
|
|
973
1062
|
const parts = [];
|
|
974
1063
|
if (publishers !== undefined && publishers.length > 0)
|
|
975
1064
|
parts.push(publishers.join("/"));
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1065
|
+
// The P-state prints as spelled (P = project draft; no P =
|
|
1066
|
+
// standard). Never added or stripped.
|
|
1067
|
+
let codeStr = codeObj?.render() ?? "";
|
|
979
1068
|
if (ieeeDraft !== undefined)
|
|
980
1069
|
codeStr += `/${ieeeDraft}`;
|
|
981
1070
|
else if (id.typed_stage?.ieeeDraftEquivalent !== undefined) {
|
|
@@ -110,15 +110,21 @@ export const UPDATE_CODES = [
|
|
|
110
110
|
{ plain: "ISO/IEC/IEEE/P16085_DIS, March 2020", replacement: "ISO/IEC/IEEE DIS P16085-2020-03" },
|
|
111
111
|
{ plain: "ANSI/IEEE Std: Outdoor Apparatus Bushings", replacement: "ANSI/IEEE Std 21-1976-11" },
|
|
112
112
|
{ plain: "Draft National Electrical Safety Code, January 2016", replacement: "IEEE PC2-2016-01" },
|
|
113
|
-
{ plain: "International Standard ISO/IEC 8802-9: 1996(E) ANSI/IEEE Std 802.9, 1996 Edition", replacement: "ISO/IEC/IEEE Std 802.9
|
|
114
|
-
{ plain: "ISO/IEC13210: 1994 (E) ANSI/IEEE Std 1003.3-1991", replacement: "ISO/IEC/IEEE
|
|
113
|
+
{ plain: "International Standard ISO/IEC 8802-9: 1996(E) ANSI/IEEE Std 802.9, 1996 Edition", replacement: "ISO/IEC 8802-9:1996(E); ANSI/IEEE Std 802.9, 1996 Edition" },
|
|
114
|
+
{ plain: "ISO/IEC13210: 1994 (E) ANSI/IEEE Std 1003.3-1991", replacement: "ISO/IEC 13210:1994 (E); ANSI/IEEE Std 1003.3-1991" },
|
|
115
115
|
{ plain: "J-STD-016-1995", replacement: "IEEE 016-1995" },
|
|
116
116
|
{ plain: "Nuclear EQ Sourcebook and Supplement", replacement: "IEEE 7438946" },
|
|
117
117
|
{ plain: "PC37.30.2/D043 Rev 18, May 2015", replacement: "IEEE PC37.30-2/D-043/R-18-2015" },
|
|
118
118
|
{ plain: "IEEE/IEC P62582 CD2 proposal, May 2017", replacement: "IEEE/IEC CD2 P62582-2017-05" },
|
|
119
|
-
{ plain: "ISO/IEC/IEEE P16326:201x WD.4a, July 2017", replacement: "ISO/IEC/IEEE P16326/D
|
|
119
|
+
{ plain: "ISO/IEC/IEEE P16326:201x WD.4a, July 2017", replacement: "ISO/IEC/IEEE P16326:2017/D=WD.4a" },
|
|
120
120
|
{ plain: "ISO/IEC/IEEE CD.1 P21839, October 2017", replacement: "ISO/IEC/IEEE CD1 P21839-2017-10" },
|
|
121
|
-
{ plain: "ISO/IEC/IEEE P16326:201x WD5, December 2017", replacement: "ISO/IEC/IEEE P16326/D
|
|
121
|
+
{ plain: "ISO/IEC/IEEE P16326:201x WD5, December 2017", replacement: "ISO/IEC/IEEE P16326:2017/D=WD.5" },
|
|
122
|
+
// The joint-publisher underscore drafts ("ISO /IEC/IEEE P24774_D1,
|
|
123
|
+
// February 2020") are the slash spellings ("/D1") of rows the ledger
|
|
124
|
+
// already pins as project drafts; the underscore form parsed through
|
|
125
|
+
// the printed-joint family and flipped the classification (pubid#430).
|
|
126
|
+
{ pattern: "\\b(ISO ?\\/ ?IEC ?\\/ ?IEEE|IEC ?\\/ ?IEEE) (P\\d+)_D(\\d+), ([A-Z][a-z]+.?) (\\d{4})\\b", replacement: "\\1 \\2/D\\3, \\4, \\5" },
|
|
127
|
+
{ pattern: "\\b(ISO ?\\/ ?IEC ?\\/ ?IEEE|IEC ?\\/ ?IEEE) (P\\d+)_D(\\d+)\\b", replacement: "\\1 \\2/D\\3" },
|
|
122
128
|
{ plain: "IEEE/ISO/IEC P42010.CD1-V1.0, April 2020", replacement: "IEEE/ISO/IEC CD1 P42010-2020-04" },
|
|
123
129
|
{ plain: "Unapproved Draft Std ISO/IEC FDIS 15288:2007(E) IEEE P15288/D3,", replacement: "ISO/IEC/IEEE FDIS Std P15288/D-3-2007" },
|
|
124
130
|
{ plain: "IS0/IEC/IEEE 8802-11:2012/Amd.5:2015(E) (Adoption of IEEE Std 802.11af-2014)", replacement: "ISO/IEC/IEEE 802.11/Amd5-2012" },
|
package/dist/flavors/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { lookupUrnParser, parseUrnFor } from "../urn/parsers.js";
|
|
1
2
|
import { oimlGrammarImplementation } from "./oiml/implementation.js";
|
|
2
3
|
import { xsfGrammarImplementation } from "./xsf/index.js";
|
|
3
4
|
import { doiGrammarImplementation } from "./doi/index.js";
|
|
@@ -47,6 +48,32 @@ import { ialaGrammarImplementation } from "./iala/index.js";
|
|
|
47
48
|
* flavor NOT listed falls back to corpus mode.
|
|
48
49
|
*/
|
|
49
50
|
export function grammarImplementation(flavor) {
|
|
51
|
+
const impl = grammarImplementationSwitch(flavor);
|
|
52
|
+
if (impl === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
// "urn:" inputs route to the flavor's URN parser where the reference
|
|
55
|
+
// implements one (impl.parseUrn or the registry in src/urn/parsers.ts
|
|
56
|
+
// mirroring lib/pubid/<flavor>/urn_parser.rb); flavors without one whose
|
|
57
|
+
// own grammar accepts URN spellings keep parsing them natively
|
|
58
|
+
// (pubid/pubid-ts#1).
|
|
59
|
+
const urnDef = lookupUrnParser(flavor);
|
|
60
|
+
if (impl.parseUrn === undefined && urnDef === undefined)
|
|
61
|
+
return impl;
|
|
62
|
+
return {
|
|
63
|
+
parse(input) {
|
|
64
|
+
if (/^urn:/i.test(input)) {
|
|
65
|
+
if (impl.parseUrn)
|
|
66
|
+
return impl.parseUrn(input);
|
|
67
|
+
return parseUrnFor(urnDef, input, impl.parse);
|
|
68
|
+
}
|
|
69
|
+
return impl.parse(input);
|
|
70
|
+
},
|
|
71
|
+
...(impl.parseUrn
|
|
72
|
+
? { parseUrn: impl.parseUrn }
|
|
73
|
+
: { parseUrn: (urn) => parseUrnFor(urnDef, urn, impl.parse) }),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function grammarImplementationSwitch(flavor) {
|
|
50
77
|
switch (flavor) {
|
|
51
78
|
case "oiml":
|
|
52
79
|
return oimlGrammarImplementation();
|
|
@@ -288,6 +288,27 @@ class JcgmBuilder extends BaseBuilder {
|
|
|
288
288
|
export function jcgmGrammarImplementation() {
|
|
289
289
|
const builder = new JcgmBuilder();
|
|
290
290
|
return {
|
|
291
|
+
// Inverse of JcgmUrnGenerator (lib/pubid/jcgm/urn_parser.rb): rebuild
|
|
292
|
+
// identifiers DIRECTLY from the URN segments — re-parsing through the
|
|
293
|
+
// grammar flattened segments and lost languages/supplements.
|
|
294
|
+
parseUrn(urn) {
|
|
295
|
+
const parts = urn.replace(/^urn:jcgm:/, "").split(":");
|
|
296
|
+
if (parts[0] === "meeting") {
|
|
297
|
+
const number = toMeetingNumber(parts[1]);
|
|
298
|
+
const date = urnDate(parts[2]);
|
|
299
|
+
return new (KIND_CLASSES.meeting)({ number, date });
|
|
300
|
+
}
|
|
301
|
+
const markerAt = parts.findIndex((seg) => seg === "corrigendum" || seg === "amendment");
|
|
302
|
+
if (markerAt === -1) {
|
|
303
|
+
return buildUrnDocument(parts);
|
|
304
|
+
}
|
|
305
|
+
const kind = (parts[markerAt] === "amendment" ? "amendment" : "corrigendum");
|
|
306
|
+
const base = buildUrnDocument(parts.slice(0, markerAt));
|
|
307
|
+
const tail = parts.slice(markerAt + 1);
|
|
308
|
+
const number = tail[0] === undefined || tail[0] === "" ? undefined : tail[0];
|
|
309
|
+
const date = urnDate(tail[1]);
|
|
310
|
+
return new (KIND_CLASSES[kind])({ base, number, date });
|
|
311
|
+
},
|
|
291
312
|
parse(input) {
|
|
292
313
|
const tree = parseGrammar(jcgmGrammar, input);
|
|
293
314
|
if (typeof tree !== "object" || tree === null || Array.isArray(tree)) {
|
|
@@ -297,4 +318,44 @@ export function jcgmGrammarImplementation() {
|
|
|
297
318
|
},
|
|
298
319
|
};
|
|
299
320
|
}
|
|
321
|
+
// lib/pubid/jcgm/urn_parser.rb build_document: Guide vs GumGuide by the
|
|
322
|
+
// "gum." number prefix; the year and language segments are matched by
|
|
323
|
+
// pattern, not position.
|
|
324
|
+
function buildUrnDocument(parts) {
|
|
325
|
+
const number = parts[0];
|
|
326
|
+
if (number === undefined || number === "") {
|
|
327
|
+
throw new Error("JCGM URN has no document number");
|
|
328
|
+
}
|
|
329
|
+
const gum = number.startsWith("gum.");
|
|
330
|
+
const rest = parts.slice(1);
|
|
331
|
+
const year = rest.find((seg) => /^(19|20)\d{2}$/.test(seg));
|
|
332
|
+
const langSegment = rest.find((seg) => /^[a-z]{2}(,[a-z]{2})*$/.test(seg));
|
|
333
|
+
const langLetters = {
|
|
334
|
+
ru: "R", fr: "F", en: "E", ar: "A", es: "S", de: "D",
|
|
335
|
+
};
|
|
336
|
+
const languages = langSegment
|
|
337
|
+
? langSegment.split(",").map((code) => new Language({ code, originalCode: langLetters[code] ?? code }))
|
|
338
|
+
: [];
|
|
339
|
+
const cls = gum ? KIND_CLASSES["gum-guide"] : KIND_CLASSES.guide;
|
|
340
|
+
const cleanNumber = gum ? number.slice("gum.".length) : number;
|
|
341
|
+
return new cls({
|
|
342
|
+
number: cleanNumber,
|
|
343
|
+
date: year ? new PubidDate({ year }) : undefined,
|
|
344
|
+
languages,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
// nil for an absent year; a component for a well-formed 19xx/20xx year;
|
|
348
|
+
// anything else rejects (no re-parse validates it any more).
|
|
349
|
+
function urnDate(segment) {
|
|
350
|
+
if (segment === undefined || segment === "")
|
|
351
|
+
return undefined;
|
|
352
|
+
if (!/^(19|20)\d{2}$/.test(segment)) {
|
|
353
|
+
throw new Error(`Invalid year in JCGM URN: ${JSON.stringify(segment)}`);
|
|
354
|
+
}
|
|
355
|
+
return new PubidDate({ year: segment });
|
|
356
|
+
}
|
|
357
|
+
// "011" reads back as "11" (Identifiers::Meeting.ordinal normalization).
|
|
358
|
+
function toMeetingNumber(segment) {
|
|
359
|
+
return String(Number(segment));
|
|
360
|
+
}
|
|
300
361
|
export { isObj };
|
|
@@ -271,12 +271,19 @@ function buildRules() {
|
|
|
271
271
|
.as("series")
|
|
272
272
|
.then(space.or(dot))
|
|
273
273
|
.as("circ_series")
|
|
274
|
-
.then(
|
|
274
|
+
.then(
|
|
275
|
+
// Bare supplement marker to the whole series, no base number
|
|
276
|
+
// ("NBS.CIRC.sup" — testsuite#5 C4).
|
|
277
|
+
((str("supp").or(str("sup"))).then(monthAbbrev().then(digits).as("supplement_month_year").or(digits.as("supp_number").then(slash, digits.as("supp_year")).as("supplement_slash_year"), str("").as("supplement_empty")).maybe())).or(
|
|
278
|
+
// Date-range supplement (no base document).
|
|
279
|
+
str("supp")
|
|
275
280
|
.then(monthAbbrev().as("supp_month_start"), digits.as("supp_year_start"), dash, monthAbbrev().as("supp_month_end"), digits.as("supp_year_end"))
|
|
276
281
|
.as("supplement_date_range")
|
|
277
282
|
.or((str("supp").or(str("sup")))
|
|
278
283
|
.then(match("[0-9]").repeat(4, 4).as("supp_year_start"), dash, match("[0-9]").repeat(4, 4).as("supp_year_end"))
|
|
279
|
-
.as("supplement_date_range")
|
|
284
|
+
.as("supplement_date_range"))).or(
|
|
285
|
+
// Base identifier + supplement.
|
|
286
|
+
(digits.as("base_number").then(str("e"), digits.as("edition_number")).or(digits
|
|
280
287
|
.as("base_number")
|
|
281
288
|
.then(lowerLetter.as("revision_letter"), digits.as("revision_number")), digits.as("base_number").then(upperLetter.as("letter_suffix")), digits.as("simple_number")))
|
|
282
289
|
.as("base_portion")
|
|
@@ -803,6 +803,7 @@ class NistBuilder {
|
|
|
803
803
|
h["supplement_month_year"] !== undefined ||
|
|
804
804
|
h["supplement_year"] !== undefined ||
|
|
805
805
|
h["supplement"] !== undefined ||
|
|
806
|
+
h["supplement_empty"] !== undefined ||
|
|
806
807
|
h["base_portion"] !== undefined) {
|
|
807
808
|
return this.buildCircularSupplement(h);
|
|
808
809
|
}
|
|
@@ -1094,9 +1095,12 @@ class NistBuilder {
|
|
|
1094
1095
|
return this.buildCircularSupplementWrapper(h);
|
|
1095
1096
|
}
|
|
1096
1097
|
const seriesValue = isObj(h["circ_series"]) ? s(h["circ_series"]["series"]) : s(h["series"]);
|
|
1098
|
+
// The dotted series literal ("NBS.CIRC") names its publisher in the
|
|
1099
|
+
// first token; the builder default would render "NIST" instead.
|
|
1100
|
+
const seriesPublisher = seriesValue?.startsWith("NBS") ? "NBS" : undefined;
|
|
1097
1101
|
if (isObj(h["supplement_date_range"])) {
|
|
1098
1102
|
const range = h["supplement_date_range"];
|
|
1099
|
-
const identifier = this.build({ series: seriesValue }, "short");
|
|
1103
|
+
const identifier = this.build({ series: seriesValue, publisher: seriesPublisher }, "short");
|
|
1100
1104
|
const ms = s(range["supp_month_start"]);
|
|
1101
1105
|
const ys = s(range["supp_year_start"]);
|
|
1102
1106
|
const me = s(range["supp_month_end"]);
|
|
@@ -1109,7 +1113,7 @@ class NistBuilder {
|
|
|
1109
1113
|
});
|
|
1110
1114
|
return identifier;
|
|
1111
1115
|
}
|
|
1112
|
-
const base = this.buildCircularSupplementBase(h, seriesValue);
|
|
1116
|
+
const base = this.buildCircularSupplementBase(h, seriesValue, seriesPublisher);
|
|
1113
1117
|
const self = base;
|
|
1114
1118
|
const raw = h["supplement_month_year"] !== undefined
|
|
1115
1119
|
? s(h["supplement_month_year"])
|
|
@@ -1119,11 +1123,11 @@ class NistBuilder {
|
|
|
1119
1123
|
self["supplement"] = NistSupplement.fromRaw(raw);
|
|
1120
1124
|
return base;
|
|
1121
1125
|
}
|
|
1122
|
-
buildCircularSupplementBase(h, seriesValue) {
|
|
1126
|
+
buildCircularSupplementBase(h, seriesValue, seriesPublisher) {
|
|
1123
1127
|
const basePortion = h["base_portion"];
|
|
1124
1128
|
if (!isObj(basePortion)) {
|
|
1125
1129
|
return this.build({
|
|
1126
|
-
publisher: s(h["publisher"]),
|
|
1130
|
+
publisher: s(h["publisher"]) ?? seriesPublisher,
|
|
1127
1131
|
series: seriesValue ?? s(h["series"]),
|
|
1128
1132
|
first_number: s(h["first_number"]),
|
|
1129
1133
|
}, s(h["parsed_format"]) ?? "short");
|
|
@@ -516,11 +516,18 @@ export class NistIdentifier extends BaseIdentifier {
|
|
|
516
516
|
else if (this.volume !== undefined && this.issue_number !== undefined) {
|
|
517
517
|
result += `${this.volume.render()}n${this.issue_number.number}`;
|
|
518
518
|
}
|
|
519
|
-
|
|
520
|
-
|
|
519
|
+
// With a number the edition glues per the NIST spec ("800-53r5");
|
|
520
|
+
// series-only editions take a dot separator ("NBS.CIRC.e2" — the
|
|
521
|
+
// attested raw spelling; testsuite#5 C4/C5).
|
|
522
|
+
if (this.edition !== undefined) {
|
|
523
|
+
result += this.number !== undefined ? this.edition.render() : `.${this.edition.render()}`;
|
|
524
|
+
}
|
|
521
525
|
if (this.version_component !== undefined)
|
|
522
526
|
result += this.version_component.render();
|
|
523
|
-
|
|
527
|
+
// A series-only supplement takes the dot itself ("NBS.CIRC.sup").
|
|
528
|
+
if (this.supplement !== undefined) {
|
|
529
|
+
result += this.number !== undefined ? this.supplementShort() : `.${this.supplementShort()}`;
|
|
530
|
+
}
|
|
524
531
|
if (this.update_component !== undefined)
|
|
525
532
|
result += this.update_component.render("mr");
|
|
526
533
|
if (this.stage !== undefined) {
|
|
@@ -47,12 +47,15 @@ function buildRules() {
|
|
|
47
47
|
.or(ref(rules, "annex_identifier"))
|
|
48
48
|
.or(ref(rules, "plus_supplement_identifier"))
|
|
49
49
|
.or(ref(rules, "trailing_supplement_identifier"))
|
|
50
|
+
.or(ref(rules, "cs_identifier"))
|
|
50
51
|
.or(ref(rules, "bulletin_identifier"))
|
|
51
52
|
.or(ref(rules, "base")));
|
|
52
53
|
// Publisher - always "OIML"
|
|
53
54
|
rule("publisher", () => str("OIML").as("publisher").then(ref(rules, "space")));
|
|
54
|
-
// Document type - single letter
|
|
55
|
-
|
|
55
|
+
// Document type - single letter. Strict family set: OIML publishes
|
|
56
|
+
// R D B G E V S documents (the estate grammar's family letters);
|
|
57
|
+
// any other letter is a rejection, not a flavor.
|
|
58
|
+
rule("doc_type", () => match("[BDEGRSV]").as("type").then(ref(rules, "space")));
|
|
56
59
|
// Bulletin locator - structured YYYY[-II[-SS]]
|
|
57
60
|
rule("bulletin_date", () => ref(rules, "space")
|
|
58
61
|
.then(ref(rules, "year_digits").as("year"))
|
|
@@ -108,9 +111,14 @@ function buildRules() {
|
|
|
108
111
|
rule("lang_single", () => match("[EFRXDSCAU]"));
|
|
109
112
|
rule("lang_multi_oiml", () => str("PO").or(str("PT")).or(str("PE")).or(str("SR")));
|
|
110
113
|
rule("lang_multi", () => match("[a-z]").repeat(2, 2));
|
|
114
|
+
// Full-word language markers as OIML prints them ("(Fra)", "(Eng)"),
|
|
115
|
+
// three or more letters, any case. Kept verbatim on the identifier;
|
|
116
|
+
// the URN lowercases.
|
|
117
|
+
rule("lang_word", () => match("[A-Za-z]").repeat(3));
|
|
111
118
|
rule("language_code", () => ref(rules, "lang_single")
|
|
112
119
|
.then(ref(rules, "slash"), ref(rules, "lang_single"))
|
|
113
120
|
.or(ref(rules, "lang_multi_oiml"))
|
|
121
|
+
.or(ref(rules, "lang_word"))
|
|
114
122
|
.or(ref(rules, "lang_single"))
|
|
115
123
|
.or(ref(rules, "lang_multi"))
|
|
116
124
|
.as("language"));
|
|
@@ -137,7 +145,25 @@ function buildRules() {
|
|
|
137
145
|
rule("trailing_supplement_identifier", () => ref(rules, "base_without_language")
|
|
138
146
|
.as("base")
|
|
139
147
|
.then(str(" "), str("Amendment").or(str("Errata")).as("trailing_marker"))
|
|
140
|
-
.then(ref(rules, "language_portion").maybe().as("language")));
|
|
148
|
+
.then(str(" ").then(ref(rules, "digits").as("number")).maybe(), ref(rules, "language_portion").maybe().as("language")));
|
|
149
|
+
// OIML-CS certification-system documents. Two head spellings
|
|
150
|
+
// ("OIML-CS" / "OIML CS") and two family-number separators
|
|
151
|
+
// ("PD-05" / "PD 05"), an "Edition N" instead of a year, and an
|
|
152
|
+
// optional parenthesized trailing amendment - "(Amendment 1)".
|
|
153
|
+
rule("cs_identifier", () => str("OIML")
|
|
154
|
+
.as("publisher")
|
|
155
|
+
.then(ref(rules, "dash")
|
|
156
|
+
.then(str("CS"))
|
|
157
|
+
.or(str(" ").then(str("CS")))
|
|
158
|
+
.as("cs_series"))
|
|
159
|
+
.then(ref(rules, "space"))
|
|
160
|
+
.then(str("PD").or(str("OD")).or(str("CID")).as("cs_family"))
|
|
161
|
+
.then(ref(rules, "dash").or(str(" ")).as("cs_separator"))
|
|
162
|
+
.then(ref(rules, "digits").as("number"))
|
|
163
|
+
.then(str(" "), str("Edition"), str(" "), ref(rules, "digits").as("edition"))
|
|
164
|
+
.then(str(" ")
|
|
165
|
+
.then(ref(rules, "lparen"), str("Amendment"), str(" "), ref(rules, "digits").as("cs_amendment"), ref(rules, "rparen"))
|
|
166
|
+
.maybe()));
|
|
141
167
|
rule("plus_supplement_identifier", () => ref(rules, "base_without_language")
|
|
142
168
|
.as("base")
|
|
143
169
|
.then(str("+"), str("Amendment").or(str("Errata")).as("plus_marker"))
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { ParseFailed, parseGrammar } from "../../grammar/engine.js";
|
|
2
2
|
import { oimlGrammar } from "./grammar.js";
|
|
3
|
-
import { buildOimlIdentifier } from "./model.js";
|
|
3
|
+
import { buildOimlIdentifier, OimlDualPublished } from "./model.js";
|
|
4
|
+
import { grammarImplementation } from "../index.js";
|
|
5
|
+
// The co-publisher prefix (only ISO confirmed so far; the gem routes via
|
|
6
|
+
// Pubid.parse's prefix table).
|
|
7
|
+
function flavorOf(input) {
|
|
8
|
+
return input.toUpperCase().startsWith("ISO ") ? "iso" : "iso";
|
|
9
|
+
}
|
|
4
10
|
/**
|
|
5
11
|
* Grammar-backed OIML on the unified model: the model classes are
|
|
6
12
|
* BaseIdentifier subclasses, so they satisfy the Identifier adapter
|
|
@@ -8,7 +14,46 @@ import { buildOimlIdentifier } from "./model.js";
|
|
|
8
14
|
*/
|
|
9
15
|
export function oimlGrammarImplementation() {
|
|
10
16
|
return {
|
|
17
|
+
// Inverse of OimlUrnGenerator (lib/pubid/oiml/urn_parser.rb): strip the
|
|
18
|
+
// "urn:oiml:" namespace, rebuild the human head "OIML <TYPE> <locator>",
|
|
19
|
+
// and re-parse. Mirrors the reference exactly — the year and language
|
|
20
|
+
// URN segments are not carried back (the reference parser reads only
|
|
21
|
+
// the type and the number-part locator).
|
|
22
|
+
parseUrn(urn) {
|
|
23
|
+
const body = urn.replace(/^urn:oiml:/i, "");
|
|
24
|
+
const parts = body.split(":");
|
|
25
|
+
const typeToken = parts[0] ?? "";
|
|
26
|
+
const number = parts[1];
|
|
27
|
+
const displayType = typeToken.toLowerCase() === "bulletin" ? "Bulletin" : typeToken.toUpperCase();
|
|
28
|
+
let text = `OIML ${displayType}`;
|
|
29
|
+
if (number !== undefined && number !== "")
|
|
30
|
+
text += ` ${number}`;
|
|
31
|
+
return this.parse(text);
|
|
32
|
+
},
|
|
11
33
|
parse(input) {
|
|
34
|
+
// The co-published form carries both identifiers joined by "|":
|
|
35
|
+
// "ISO 4064-1:2024|OIML R 49-1:2024". The first side is another
|
|
36
|
+
// flavor's identifier (routed by prefix, the Ruby Pubid.parse
|
|
37
|
+
// counterpart); the second is the OIML side.
|
|
38
|
+
if (input.includes("|") && !input.startsWith("|") && !input.endsWith("|")) {
|
|
39
|
+
// Each side may be either flavor ("ISO …|OIML R …" and the
|
|
40
|
+
// reversed print): the OIML side parses through this grammar, the
|
|
41
|
+
// co-publisher's through its own (routed by prefix).
|
|
42
|
+
const parts = input.split("|");
|
|
43
|
+
const members = parts.map((part) => {
|
|
44
|
+
const s = part.trim();
|
|
45
|
+
if (s.toUpperCase().startsWith("OIML")) {
|
|
46
|
+
return buildOimlIdentifier(parseGrammar(oimlGrammar, s));
|
|
47
|
+
}
|
|
48
|
+
const impl = grammarImplementation(flavorOf(s));
|
|
49
|
+
if (impl === undefined)
|
|
50
|
+
throw new ParseFailed(`OIML: no flavor for ${s}`, 0);
|
|
51
|
+
return impl.parse(s);
|
|
52
|
+
});
|
|
53
|
+
if (members.length === 2 && members[0] !== undefined && members[1] !== undefined) {
|
|
54
|
+
return new OimlDualPublished({ first: members[0], second: members[1] });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
12
57
|
return buildOimlIdentifier(parseGrammar(oimlGrammar, input));
|
|
13
58
|
},
|
|
14
59
|
};
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
import type { Tree } from "../../grammar/engine.js";
|
|
2
2
|
import { BaseIdentifier } from "../../model/identifier.js";
|
|
3
|
+
import type { AttributeTable } from "../../model/attribute.js";
|
|
4
|
+
declare abstract class OimlBase extends BaseIdentifier {
|
|
5
|
+
readonly publisher: string | undefined;
|
|
6
|
+
readonly language: string | undefined;
|
|
7
|
+
readonly parsed_format: string | undefined;
|
|
8
|
+
effectiveFormat(): "long" | "short";
|
|
9
|
+
}
|
|
3
10
|
/** ---- builder (lib/pubid/oiml/builder.rb) ---- */
|
|
11
|
+
export declare class OimlDualPublished extends OimlBase {
|
|
12
|
+
static polymorphicName: string;
|
|
13
|
+
static get attributes(): AttributeTable;
|
|
14
|
+
static get mappings(): never[];
|
|
15
|
+
readonly first: BaseIdentifier;
|
|
16
|
+
readonly second: BaseIdentifier;
|
|
17
|
+
constructor(attrs?: Record<string, unknown>);
|
|
18
|
+
render(): string;
|
|
19
|
+
toUrn(): string;
|
|
20
|
+
root(): BaseIdentifier;
|
|
21
|
+
}
|
|
4
22
|
export declare function buildOimlIdentifier(tree: Tree): BaseIdentifier;
|
|
23
|
+
export {};
|