@pubid/pubid 0.1.34 → 0.1.36
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/artifact/corpus.json.gz +0 -0
- package/dist/flavors/api/grammar.js +9 -2
- package/dist/flavors/api/index.js +31 -2
- package/dist/flavors/bipm/index.js +89 -14
- package/dist/flavors/iec/index.js +15 -4
- package/dist/flavors/iec/model.js +26 -10
- package/dist/flavors/ieee/model.js +42 -5
- package/dist/pubid-urn/schemas/iec.js +6 -0
- package/package.json +2 -2
package/artifact/corpus.json.gz
CHANGED
|
Binary file
|
|
@@ -53,9 +53,16 @@ function buildRules() {
|
|
|
53
53
|
rule("typeless_identifier", () => ref(rules, "publisher")
|
|
54
54
|
.then(docTail())
|
|
55
55
|
.then(ref(rules, "reaffirmation").maybe()));
|
|
56
|
-
rule("second_identifier", () =>
|
|
56
|
+
rule("second_identifier", () =>
|
|
57
|
+
// The reference's grammar binds the second side's captures under
|
|
58
|
+
// second_* names (second_type/second_number/second_part), so the
|
|
59
|
+
// builder can fold first+second and let the second side overwrite.
|
|
60
|
+
ref(rules, "doc_type").as("second_type")
|
|
57
61
|
.then(ref(rules, "space"))
|
|
58
|
-
.then(
|
|
62
|
+
.then(match("[0-9A-Z]").repeat(1, Infinity).as("second_number")
|
|
63
|
+
.then((ref(rules, "dash").then((match("[0-9A-Z]").repeat(1, Infinity)).as("second_part"))).maybe())
|
|
64
|
+
.then(ref(rules, "part_notation").maybe())
|
|
65
|
+
.then(ref(rules, "date_dash").or(ref(rules, "date_colon")).maybe())));
|
|
59
66
|
rule("combined_identifier", () => ref(rules, "mpms_identifier")
|
|
60
67
|
.or(ref(rules, "typed_identifier"))
|
|
61
68
|
.or(ref(rules, "typeless_identifier"))
|
|
@@ -52,13 +52,42 @@ function assignAttributes(identifier, data) {
|
|
|
52
52
|
self[key] = realized;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
// The bound grammar wraps the combined form's first/second identifiers
|
|
56
|
+
// under "first"/"second"; the reference folds both wrappers into one
|
|
57
|
+
// hash in parse order, so the second side's number/part/year overwrite
|
|
58
|
+
// the first's — and select_class saw no top-level :type, so a combined
|
|
59
|
+
// form always built the typeless class. Reproduce both.
|
|
60
|
+
const SECOND_KEYS = ["second_number", "second_part", "second_year", "second_type"];
|
|
61
|
+
const SECOND_OVERWRITES = {
|
|
62
|
+
second_number: "number",
|
|
63
|
+
second_part: "part",
|
|
64
|
+
second_year: "year",
|
|
65
|
+
};
|
|
66
|
+
function unflattenCombined(data) {
|
|
67
|
+
if (!SECOND_KEYS.some((k) => k in data))
|
|
68
|
+
return data;
|
|
69
|
+
const out = { ...data };
|
|
70
|
+
delete out["type"];
|
|
71
|
+
for (const [source, target] of Object.entries(SECOND_OVERWRITES)) {
|
|
72
|
+
if (source in out)
|
|
73
|
+
out[target] = out[source];
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
55
77
|
export function buildApiIdentifier(tree) {
|
|
56
78
|
const data = flatten(tree);
|
|
57
|
-
const
|
|
79
|
+
const first = isObj(data["first"]) ? data["first"] : undefined;
|
|
80
|
+
const second = isObj(data["second"]) ? data["second"] : undefined;
|
|
81
|
+
// Parse-order fold: the first side's captures, then the second's.
|
|
82
|
+
const combined = { ...data, ...(first ?? {}), ...(second ?? {}) };
|
|
83
|
+
delete combined["first"];
|
|
84
|
+
delete combined["second"];
|
|
85
|
+
const unflattened = unflattenCombined(combined);
|
|
86
|
+
const typeStr = strv(unflattened["type"]);
|
|
58
87
|
const klass = (typeStr !== undefined ? TYPE_CLASS_MAP[typeStr] : undefined)
|
|
59
88
|
?? TypelessStandardClass;
|
|
60
89
|
const identifier = new klass();
|
|
61
|
-
assignAttributes(identifier,
|
|
90
|
+
assignAttributes(identifier, unflattened);
|
|
62
91
|
return identifier;
|
|
63
92
|
}
|
|
64
93
|
export function apiGrammarImplementation() {
|
|
@@ -4,13 +4,14 @@ import { BaseIdentifier, registerType } from "../../model/identifier.js";
|
|
|
4
4
|
import { extendAttributes, keyValue } from "../../model/attribute.js";
|
|
5
5
|
import { BaseUrnGenerator } from "../../model/urn-generator.js";
|
|
6
6
|
/**
|
|
7
|
-
* 1:1 port of lib/pubid/bipm/ —
|
|
7
|
+
* 1:1 port of lib/pubid/bipm/ — corpus rows across six families:
|
|
8
8
|
* committee documents (REC/RES/DECN/ACT/DECL in short and long EN/FR
|
|
9
|
-
* forms
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* forms, with the appendix/annex complement and the publisher-prefixed
|
|
10
|
+
* decision spelling), meetings (EN ordinals and double-dash forms, FR
|
|
11
|
+
* <sup>e</sup> réunion), the Metrologia journal, the SI Brochure and
|
|
12
|
+
* its derived products, mises en pratique (incl. Rapport BIPM-YYYY/NN),
|
|
13
|
+
* and CC guides. Historic group names (CCDS) and two-letter language
|
|
14
|
+
* codes (EN/FR) normalize at parse time and never reach the hash.
|
|
14
15
|
*/
|
|
15
16
|
const GROUPS = [
|
|
16
17
|
"CIPM", "CGPM", "JCRB", "CCTF", "CCQM", "CCT", "CCL", "CCAUV",
|
|
@@ -66,15 +67,31 @@ function buildRules() {
|
|
|
66
67
|
const connective = () => str("de la").or(str("du"));
|
|
67
68
|
const ordinal = () => str("st").or(str("nd"), str("rd"), str("th"));
|
|
68
69
|
const meetingWordFr = () => str("Réunion").or(str("réunion"));
|
|
69
|
-
// The trailing date is optional so "CCTF REC 2" parses date-less.
|
|
70
|
+
// The trailing date is optional so "CCTF REC 2" parses date-less. The
|
|
71
|
+
// appendix/annex complement tail ("CIPM DECN 112 A3.5 (2022, F)")
|
|
72
|
+
// rides the number: "A" + digits [ "." + digits ].
|
|
70
73
|
rule("committee_short", () => rules["group"]
|
|
71
|
-
.then(space, rules["type_abbrev"], space.then(rules["number"]
|
|
74
|
+
.then(space, rules["type_abbrev"], space.then(rules["number"], space
|
|
75
|
+
.then(str("A").then(digits.as("appendix"), str(".").then(digits.as("annex")).maybe()))
|
|
76
|
+
.maybe()).maybe(), space.then(rules["year_paren"]).maybe())
|
|
72
77
|
.as("committee_short"));
|
|
78
|
+
// The publisher-prefixed decision form the corpus cites
|
|
79
|
+
// ("BIPM DECN CIPM/101-1 (2012, EN)"): a literal BIPM publisher lead,
|
|
80
|
+
// the outcome type, then the body-qualified resource-specific-id
|
|
81
|
+
// "{body}/{id}". It names the same document as the unprefixed
|
|
82
|
+
// "<body> <TYPE> <id>" form, so the builder dispatches it through the
|
|
83
|
+
// same CommitteeDocument path.
|
|
84
|
+
rule("committee_prefixed", () => str("BIPM")
|
|
85
|
+
.then(space, rules["type_abbrev"], space, rules["group"], str("/"), rules["number"], space.then(rules["year_paren"]).maybe())
|
|
86
|
+
.as("committee_prefixed"));
|
|
73
87
|
rule("committee_long_en", () => rules["group"]
|
|
74
88
|
.then(space, rules["type_name_en"], space.then(rules["number"]).maybe(), space.then(rules["year_paren_nolang"]).maybe())
|
|
75
89
|
.as("committee_long_en"));
|
|
76
90
|
rule("committee_long_fr", () => rules["type_name_fr"]
|
|
77
91
|
.then(space.then(rules["number"]).maybe(), space, connective(), space, rules["group"], space.then(rules["year_paren_nolang"]).maybe())
|
|
92
|
+
// The corpus-cited French body-qualified form ("Décision
|
|
93
|
+
// CIPM/101-1 (2012)").
|
|
94
|
+
.or(rules["type_name_fr"].then(space, rules["group"], str("/"), rules["number"], space.then(rules["year_paren_nolang"]).maybe()))
|
|
78
95
|
.as("committee_long_fr"));
|
|
79
96
|
// Loose consumer form: a French type name in group-leading order.
|
|
80
97
|
rule("committee_group_fr", () => rules["group"]
|
|
@@ -87,6 +104,9 @@ function buildRules() {
|
|
|
87
104
|
rule("meeting_en", () => rules["group"]
|
|
88
105
|
.then(space, rules["number"], ordinal(), space, str("Meeting"), space.then(rules["year_paren"]).maybe())
|
|
89
106
|
.or(rules["group"].then(space, str("Meeting"), space, rules["number"], space.then(rules["year_paren"]).maybe()))
|
|
107
|
+
// The double-dash consumer spellings ("CGPM -- Meeting 1 (1889)").
|
|
108
|
+
.or(rules["group"].then(space, str("--"), space, rules["number"], ordinal(), space, str("Meeting"), space.then(rules["year_paren"]).maybe()))
|
|
109
|
+
.or(rules["group"].then(space, str("--"), space, str("Meeting"), space, rules["number"], space.then(rules["year_paren"]).maybe()))
|
|
90
110
|
.as("meeting_en"));
|
|
91
111
|
rule("meeting_fr", () => rules["group"]
|
|
92
112
|
.then(space, rules["number"], str("<sup>e</sup>"), space, str("réunion"), space.then(rules["year_paren"]).maybe())
|
|
@@ -102,10 +122,23 @@ function buildRules() {
|
|
|
102
122
|
rule("edition", () => digits.then(str("e")).as("edition"));
|
|
103
123
|
rule("version", () => str("v").then(match("[0-9.]").repeat(1, Infinity)).as("version"));
|
|
104
124
|
rule("years", () => digits.then(str("/").then(digits).maybe()).as("years"));
|
|
105
|
-
rule("si_brochure", () =>
|
|
125
|
+
rule("si_brochure", () =>
|
|
126
|
+
// The guide's French publication identifier leads with
|
|
127
|
+
// "Brochure sur le SI" instead of the English brochure name; all
|
|
128
|
+
// three leads render in the canonical "BIPM SI Brochure …" spelling.
|
|
129
|
+
str("BIPM SI Brochure")
|
|
106
130
|
.or(str("SI Brochure"))
|
|
107
|
-
.
|
|
131
|
+
.or(str("Brochure sur le SI"))
|
|
132
|
+
.then(space, str("sur le SI").then(space).maybe(), rules["edition"],
|
|
133
|
+
// The version and the language code are optional (the guide's
|
|
134
|
+
// language-independent form, "SI Brochure 9e (2019, E)").
|
|
135
|
+
space.then(rules["version"]).maybe(), space, str("("), rules["years"], str(", ").then(lang()).maybe(), str(")"))
|
|
108
136
|
.as("si_brochure"));
|
|
137
|
+
// The corpus-cited ordinal-edition spelling ("SI Brochure 9th edition
|
|
138
|
+
// (2019)") normalizes the English ordinal onto the canonical "9e".
|
|
139
|
+
rule("si_brochure_ordinal_edition", () => str("SI Brochure")
|
|
140
|
+
.then(space, digits.then(ordinal()).as("ordinal_edition"), space, str("edition"), space.then(rules["year_paren"]).maybe())
|
|
141
|
+
.as("si_brochure_ordinal_edition"));
|
|
109
142
|
rule("brochure_variant", () => str("Appendix")
|
|
110
143
|
.then(space, digits)
|
|
111
144
|
.or(str("Concise"), str("FAQ"))
|
|
@@ -134,7 +167,7 @@ function buildRules() {
|
|
|
134
167
|
.or(guideBody())
|
|
135
168
|
.as("guide"));
|
|
136
169
|
rule("group_leading", () => rules["meeting_en"].or(rules["meeting_fr"], rules["committee_short"], rules["committee_long_en"], rules["committee_group_fr"], rules["committee_bare"]));
|
|
137
|
-
rule("identifier", () => rules["metrologia"].or(rules["si_brochure"], rules["si_brochure_variant"], rules["si_brochure_section"], rules["mep"], rules["guide"], rules["committee_long_fr"], rules["group_leading"]));
|
|
170
|
+
rule("identifier", () => rules["metrologia"].or(rules["si_brochure"], rules["si_brochure_variant"], rules["si_brochure_section"], rules["si_brochure_ordinal_edition"], rules["mep"], rules["guide"], rules["committee_long_fr"], rules["committee_prefixed"], rules["group_leading"]));
|
|
138
171
|
rule("root", () => rules["identifier"]);
|
|
139
172
|
return rules;
|
|
140
173
|
}
|
|
@@ -183,6 +216,16 @@ function ordinalSuffix(number) {
|
|
|
183
216
|
function frenchConnective(group) {
|
|
184
217
|
return group === "CGPM" ? "de la" : "du";
|
|
185
218
|
}
|
|
219
|
+
/** The appendix/annex complement folds into the identifier segment as
|
|
220
|
+
* "-a{appendix}[.{annex}]" (ruby committee_number_segment); a bare
|
|
221
|
+
* number is digits and dashes only, so the suffix is unambiguous. */
|
|
222
|
+
function committeeNumberSegment(id) {
|
|
223
|
+
if (id.number === undefined)
|
|
224
|
+
return "";
|
|
225
|
+
if (id.appendix === undefined)
|
|
226
|
+
return id.number;
|
|
227
|
+
return `${id.number}-a${id.appendix}${id.annex === undefined ? "" : `.${id.annex}`}`;
|
|
228
|
+
}
|
|
186
229
|
export class BipmIdentifier extends BaseIdentifier {
|
|
187
230
|
static attributes = extendAttributes(BaseIdentifier, BIPM_ATTRS);
|
|
188
231
|
static mappings = BIPM_MAPPINGS;
|
|
@@ -205,7 +248,7 @@ class BipmUrnGenerator extends BaseUrnGenerator {
|
|
|
205
248
|
if (id instanceof BipmCommitteeDocument) {
|
|
206
249
|
const parts = [
|
|
207
250
|
"urn", "bipm", id.group?.toLowerCase() ?? "",
|
|
208
|
-
id.type_code?.toLowerCase() ?? "", id
|
|
251
|
+
id.type_code?.toLowerCase() ?? "", committeeNumberSegment(id),
|
|
209
252
|
];
|
|
210
253
|
if (id.year !== undefined)
|
|
211
254
|
parts.push(String(id.year));
|
|
@@ -248,7 +291,15 @@ class BipmCommitteeDocument extends BipmIdentifier {
|
|
|
248
291
|
}
|
|
249
292
|
const lang = this.language === undefined ? "" : `, ${this.language}`;
|
|
250
293
|
const type = this.type_code === undefined ? "" : ` ${this.type_code}`;
|
|
251
|
-
return `${this.group ?? ""}${type}${this.numberSegment()}${this.yearSegment(lang)}`;
|
|
294
|
+
return `${this.group ?? ""}${type}${this.numberSegment()}${this.appendixSegment()}${this.yearSegment(lang)}`;
|
|
295
|
+
}
|
|
296
|
+
/** The complement tail "{identifier} A{appendix}[.{annex}]" — parts and
|
|
297
|
+
* subparts are dash forms inside `number` itself; appendices and
|
|
298
|
+
* annexes are this separate tail. */
|
|
299
|
+
appendixSegment() {
|
|
300
|
+
if (this.appendix === undefined)
|
|
301
|
+
return "";
|
|
302
|
+
return ` A${this.appendix}${this.annex === undefined ? "" : `.${this.annex}`}`;
|
|
252
303
|
}
|
|
253
304
|
}
|
|
254
305
|
registerType(BipmCommitteeDocument);
|
|
@@ -294,7 +345,8 @@ class BipmSiBrochure extends BipmIdentifier {
|
|
|
294
345
|
}
|
|
295
346
|
const phrase = this.language === "F" ? "sur le SI " : "";
|
|
296
347
|
const lang = this.language === undefined ? "" : `, ${this.language}`;
|
|
297
|
-
|
|
348
|
+
const version = this.version === undefined ? "" : ` ${this.version}`;
|
|
349
|
+
return `BIPM SI Brochure ${phrase}${this.edition}${version} (${this.years}${lang})`;
|
|
298
350
|
}
|
|
299
351
|
}
|
|
300
352
|
registerType(BipmSiBrochure);
|
|
@@ -336,6 +388,12 @@ class BipmBuilder {
|
|
|
336
388
|
};
|
|
337
389
|
if (tree["committee_short"] !== undefined)
|
|
338
390
|
return this.buildCommittee(node("committee_short"), "short");
|
|
391
|
+
if (tree["committee_prefixed"] !== undefined) {
|
|
392
|
+
// The corpus's "BIPM DECN CIPM/101-1 (2012, EN)" publisher-prefixed
|
|
393
|
+
// spelling names the same document as the unprefixed short form; the
|
|
394
|
+
// BIPM lead is the constant publisher, so it carries no attribute.
|
|
395
|
+
return this.buildCommittee(node("committee_prefixed"), "short");
|
|
396
|
+
}
|
|
339
397
|
if (tree["committee_long_en"] !== undefined)
|
|
340
398
|
return this.buildCommittee(node("committee_long_en"), "long", "E");
|
|
341
399
|
if (tree["committee_bare"] !== undefined)
|
|
@@ -373,6 +431,21 @@ class BipmBuilder {
|
|
|
373
431
|
const n = node("si_brochure_section");
|
|
374
432
|
return new BipmSiBrochure({ part: s(n["part"]) });
|
|
375
433
|
}
|
|
434
|
+
if (tree["si_brochure_ordinal_edition"] !== undefined) {
|
|
435
|
+
// The guide prints the edition "9e"; the corpus-cited "9th edition"
|
|
436
|
+
// spelling is the English ordinal form of the same edition,
|
|
437
|
+
// normalized onto the canonical "9e" value. The version is absent
|
|
438
|
+
// from the input, so it stays nil and drops from the hash.
|
|
439
|
+
const n = node("si_brochure_ordinal_edition");
|
|
440
|
+
const edition = `${(s(n["ordinal_edition"]) ?? "").replace(/(st|nd|rd|th)$/, "")}e`;
|
|
441
|
+
const year = s(n["year"]);
|
|
442
|
+
return new BipmSiBrochure({
|
|
443
|
+
number: edition,
|
|
444
|
+
edition,
|
|
445
|
+
...(year !== undefined ? { years: year } : {}),
|
|
446
|
+
language: normalizeLanguage(s(n["language"])),
|
|
447
|
+
});
|
|
448
|
+
}
|
|
376
449
|
if (tree["mep"] !== undefined) {
|
|
377
450
|
const n = node("mep");
|
|
378
451
|
const mepCode = s(n["mep_code"]);
|
|
@@ -407,6 +480,8 @@ class BipmBuilder {
|
|
|
407
480
|
...(year !== undefined ? { year: Number(year) } : {}),
|
|
408
481
|
language: normalizeLanguage(language ?? s(n["language"])),
|
|
409
482
|
form,
|
|
483
|
+
appendix: s(n["appendix"]),
|
|
484
|
+
annex: s(n["annex"]),
|
|
410
485
|
});
|
|
411
486
|
}
|
|
412
487
|
buildMeeting(n, language) {
|
|
@@ -133,12 +133,16 @@ class IecBuilder {
|
|
|
133
133
|
const kind = typedStage.typeCode === "wp" ? "is" : typedStage.typeCode;
|
|
134
134
|
const attrs = {};
|
|
135
135
|
this.commonAttrs(tree, attrs);
|
|
136
|
-
// TRF with an embedded CISPR document: the
|
|
137
|
-
//
|
|
136
|
+
// TRF with an embedded CISPR document: the form itself has no number
|
|
137
|
+
// — the document it reports on is built as the nested identifier and
|
|
138
|
+
// serializes under `cispr_identifier` (pubid#687). The runtime
|
|
139
|
+
// columns stay for the renderer and the official-URN slot.
|
|
138
140
|
if (tree["trf_org"] !== undefined) {
|
|
139
141
|
const cisprComponents = numberComponents(tree["number_with_part"]);
|
|
140
142
|
attrs["cispr_number"] = cisprComponents["number"];
|
|
141
143
|
attrs["cispr_part"] = cisprComponents["part"];
|
|
144
|
+
const cispr = new IEC_CLASSES.KIND_CLASSES["is"]({ publisher: "CISPR", ...cisprComponents });
|
|
145
|
+
attrs["cispr_identifier"] = cispr;
|
|
142
146
|
delete attrs["number"];
|
|
143
147
|
delete attrs["part"];
|
|
144
148
|
delete attrs["subpart"];
|
|
@@ -300,9 +304,14 @@ class IecBuilder {
|
|
|
300
304
|
});
|
|
301
305
|
}
|
|
302
306
|
buildWorkingProgramme(tree) {
|
|
307
|
+
// The grammar captures a trailing space on wp_type ("PWI TR 100-36"
|
|
308
|
+
// -> "TR "); ruby strips it at the construction site (its builder's
|
|
309
|
+
// :wp_type cast) so the stored value — and the hash and URN — is the
|
|
310
|
+
// stripped form, an all-space value dropping out entirely.
|
|
311
|
+
const wpTypeRaw = s(tree["wp_type"])?.replace(/\s+$/, "");
|
|
303
312
|
const attrs = {
|
|
304
313
|
wp_stage: s(tree["wp_stage"]),
|
|
305
|
-
wp_type:
|
|
314
|
+
wp_type: wpTypeRaw === "" ? undefined : wpTypeRaw,
|
|
306
315
|
};
|
|
307
316
|
if (tree["wp_raw"] !== undefined) {
|
|
308
317
|
// TC-style name ("SyCCOMM-1"): split the trailing number as the part.
|
|
@@ -329,7 +338,9 @@ class IecBuilder {
|
|
|
329
338
|
buildWorkingDocument(tree) {
|
|
330
339
|
const attrs = {
|
|
331
340
|
technical_committee: s(tree["technical_committee"]),
|
|
332
|
-
|
|
341
|
+
// The document number is the inherited `number` (pubid#687: the
|
|
342
|
+
// parallel wd_number slot used to serialize nowhere).
|
|
343
|
+
number: s(tree["wd_number"]),
|
|
333
344
|
wd_language: s(tree["wd_language"]),
|
|
334
345
|
wd_stage: s(tree["wd_stage"]),
|
|
335
346
|
};
|
|
@@ -190,7 +190,10 @@ function iecSingleClass(kind) {
|
|
|
190
190
|
registerType(IecSingleIdentifier);
|
|
191
191
|
return IecSingleIdentifier;
|
|
192
192
|
}
|
|
193
|
-
/** Working documents: the
|
|
193
|
+
/** Working documents: the committee/number/language/stage identity
|
|
194
|
+
* serializes (pubid#687) — the document number is the inherited
|
|
195
|
+
* `number`, and the five plain strings sit in the hash under their own
|
|
196
|
+
* keys (ruby WorkingDocument's key_value block, render_default off). */
|
|
194
197
|
function iecWorkingDocumentClass() {
|
|
195
198
|
class IecWorkingDocument extends IecIdentifier {
|
|
196
199
|
static polymorphicName = "pubid:iec:working-document";
|
|
@@ -199,11 +202,10 @@ function iecWorkingDocumentClass() {
|
|
|
199
202
|
wp_stage: { type: "string" },
|
|
200
203
|
wp_type: { type: "string" },
|
|
201
204
|
technical_committee: { type: "string" },
|
|
202
|
-
wd_number: { type: "string" },
|
|
203
205
|
wd_language: { type: "string" },
|
|
204
206
|
wd_stage: { type: "string" },
|
|
205
207
|
});
|
|
206
|
-
static mappings = keyValue({ wire: "number", to: "number" }, { wire: "part", to: "part" }, { wire: "subpart", to: "subpart" }, { wire: "edition", to: "edition" });
|
|
208
|
+
static mappings = keyValue({ wire: "number", to: "number" }, { wire: "part", to: "part" }, { wire: "subpart", to: "subpart" }, { wire: "edition", to: "edition" }, { wire: "technical_committee", to: "technical_committee" }, { wire: "wd_language", to: "wd_language" }, { wire: "wd_stage", to: "wd_stage" }, { wire: "wp_stage", to: "wp_stage" }, { wire: "wp_type", to: "wp_type" });
|
|
207
209
|
render() {
|
|
208
210
|
if (this.wp_stage !== undefined) {
|
|
209
211
|
const parts = [this.wp_stage];
|
|
@@ -225,8 +227,8 @@ function iecWorkingDocumentClass() {
|
|
|
225
227
|
const parts = [];
|
|
226
228
|
if (this.technical_committee !== undefined)
|
|
227
229
|
parts.push(this.technical_committee);
|
|
228
|
-
if (this.
|
|
229
|
-
let numPart = this.
|
|
230
|
+
if (this.number !== undefined) {
|
|
231
|
+
let numPart = String(this.number);
|
|
230
232
|
if (this.wd_language !== undefined)
|
|
231
233
|
numPart += `(${this.wd_language})`;
|
|
232
234
|
parts.push(numPart);
|
|
@@ -249,6 +251,10 @@ function iecTrfClass() {
|
|
|
249
251
|
cispr_number: { type: "string" },
|
|
250
252
|
cispr_part: { type: "string" },
|
|
251
253
|
trf_info: { type: "string" },
|
|
254
|
+
// The embedded CISPR document is part of the form's identity and
|
|
255
|
+
// serializes under its own key (pubid#687), dispatching on the
|
|
256
|
+
// nested _type like ruby's polymorphic attribute.
|
|
257
|
+
cispr_identifier: { type: IecIdentifier },
|
|
252
258
|
});
|
|
253
259
|
static mappings = keyValue({ wire: "number", to: "number" }, { wire: "part", to: "part" }, { wire: "subpart", to: "subpart" }, { wire: "year", to: "date", toWire: (m) => m["date"]?.year, fromWire: yearFromWire }, { wire: "month", to: "date", toWire: (m) => m["date"]?.month, fromWire: () => undefined }, { wire: "day", to: "date", toWire: (m) => m["date"]?.day, fromWire: () => undefined }, {
|
|
254
260
|
wire: "publisher",
|
|
@@ -263,13 +269,23 @@ function iecTrfClass() {
|
|
|
263
269
|
return list !== undefined && list.length > 0 ? list : undefined;
|
|
264
270
|
},
|
|
265
271
|
fromWire: (h) => (Array.isArray(h["copublishers"]) ? h["copublishers"].map(String) : undefined),
|
|
266
|
-
});
|
|
272
|
+
}, { wire: "cispr_identifier", to: "cispr_identifier" });
|
|
267
273
|
render() {
|
|
268
274
|
let result = this.publisherPortion() + " ";
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
275
|
+
// The parse path carries the CISPR document in the runtime columns;
|
|
276
|
+
// a fromHash-built form carries the nested identifier — read the
|
|
277
|
+
// document from whichever one is set (ruby renderer).
|
|
278
|
+
const cispr = this.cispr_identifier;
|
|
279
|
+
const cisprNumber = this.cispr_number ??
|
|
280
|
+
(cispr?.number === undefined || cispr.number === null ? undefined : String(cispr.number));
|
|
281
|
+
const cisprPart = this.cispr_part ??
|
|
282
|
+
(cispr?.part === undefined || cispr.part === null || cispr.part === ""
|
|
283
|
+
? undefined
|
|
284
|
+
: String(cispr.part));
|
|
285
|
+
if (cisprNumber !== undefined) {
|
|
286
|
+
let numStr = cisprNumber;
|
|
287
|
+
if (cisprPart !== undefined && cisprPart !== "")
|
|
288
|
+
numStr += `-${cisprPart}`;
|
|
273
289
|
let cisprStr = `CISPR ${numStr}`;
|
|
274
290
|
if (this.date !== undefined)
|
|
275
291
|
cisprStr += `:${this.date.render()}`;
|
|
@@ -227,10 +227,18 @@ export class IeeeDraft {
|
|
|
227
227
|
// are accepted aliases. Captures [stage, iteration]. The iteration
|
|
228
228
|
// ordinal stays short (one or two digits): a glued D= date
|
|
229
229
|
// ("-201908", the pubid#203 convention) must ride the version body
|
|
230
|
-
// verbatim, not fold into the iteration slot.
|
|
231
|
-
|
|
230
|
+
// verbatim, not fold into the iteration slot. The glued designator
|
|
231
|
+
// date decomposes ("-201908" -> year + zero-padded month): the
|
|
232
|
+
// reference's builder carries it on the parse-path Draft object
|
|
233
|
+
// (iso_stage + year + month), and the render's stage-draft guard
|
|
234
|
+
// reads the stage — a re-parse that kept it in the version body
|
|
235
|
+
// would print the type word the designator face never takes.
|
|
236
|
+
// FCD rides the stage list (the ECHO_SUFFIX spelling).
|
|
237
|
+
const compound = /=D?(PWI|NP|WD|CDV|FDIS|FCD|DIS|CD)(\d{1,2}[a-z]?)?(?:[.-](\d{1,2}[a-z]?))?(?:-(\d{4})(\d{2}))?$/;
|
|
232
238
|
let isoStage;
|
|
233
239
|
let isoIteration;
|
|
240
|
+
let glueYear;
|
|
241
|
+
let glueMonth;
|
|
234
242
|
const cm = compound.exec(body);
|
|
235
243
|
if (cm !== null) {
|
|
236
244
|
// The glued digits ride the STAGE ("D=CD1", matching the reference's
|
|
@@ -238,6 +246,8 @@ export class IeeeDraft {
|
|
|
238
246
|
// ".iter" slot ("=DIS.3").
|
|
239
247
|
isoStage = cm[1] + (cm[2] ?? "");
|
|
240
248
|
isoIteration = cm[3];
|
|
249
|
+
glueYear = cm[4];
|
|
250
|
+
glueMonth = cm[5];
|
|
241
251
|
body = body.slice(0, cm.index);
|
|
242
252
|
}
|
|
243
253
|
const monthAlt = Object.keys(DRAFT_MONTH_NAMES)
|
|
@@ -247,7 +257,16 @@ export class IeeeDraft {
|
|
|
247
257
|
const suffix = new RegExp(`(?:(, | )(${monthAlt})(?: (\\d{1,2}))?(?:, | )|, | )((?:19|20)\\d{2}[a-z]{0,2})$`);
|
|
248
258
|
const m = suffix.exec(body);
|
|
249
259
|
if (m === null) {
|
|
250
|
-
|
|
260
|
+
// A glue-consumed body ("=FDIS-201908") carries the designator's
|
|
261
|
+
// own date on the year/month slots (the parse-path Draft object's
|
|
262
|
+
// shape), so the render re-composes the glued face verbatim.
|
|
263
|
+
return new IeeeDraft({
|
|
264
|
+
version: body,
|
|
265
|
+
iso_stage: isoStage,
|
|
266
|
+
iso_iteration: isoIteration,
|
|
267
|
+
year: glueYear,
|
|
268
|
+
month: glueMonth,
|
|
269
|
+
});
|
|
251
270
|
}
|
|
252
271
|
return new IeeeDraft({
|
|
253
272
|
version: body.slice(0, m.index),
|
|
@@ -536,7 +555,15 @@ export function renderIeeeBase(id) {
|
|
|
536
555
|
// publisher -- the ISO-led joint drafts (pubid#597) print it too.
|
|
537
556
|
const shouldRenderType = /^(IEEE|AIEE)/.test(id.publisher ?? "") || isProjectDraft;
|
|
538
557
|
const stageObj = id.typed_stage;
|
|
539
|
-
|
|
558
|
+
// An ISO/IEC stage-token draft (CD1, FDIS - the draft carries the
|
|
559
|
+
// stage) is its own draft marker: the designator face it renders
|
|
560
|
+
// (".../D=CD1", pubid#203) never takes the word Draft, so the type
|
|
561
|
+
// word is skipped whatever the publisher arrangement - the joint rows
|
|
562
|
+
// ("IEEE/IEC Draft P60076-57-129/D=FDIS-201707") would otherwise
|
|
563
|
+
// print Draft on one construction path and drop it on the re-parsed
|
|
564
|
+
// designation path, and the render would not re-parse to itself.
|
|
565
|
+
const stageDraft = id.draftObject()?.isoStage !== undefined;
|
|
566
|
+
if (shouldRenderType && !stageDraft && !(stageObj?.projectStatus === true) &&
|
|
540
567
|
((id.type !== undefined && id.type.trim() !== "" && id.type !== "P") ||
|
|
541
568
|
isProjectDraft) &&
|
|
542
569
|
!/unapproved/i.test(id.draft_status ?? "")) {
|
|
@@ -1298,7 +1325,17 @@ export const JointDevelopmentClass = ieeeClass({ kind: "joint-development" }, {
|
|
|
1298
1325
|
let result = parts.join(" ");
|
|
1299
1326
|
if (id.year !== undefined && !designatorCarriesYear &&
|
|
1300
1327
|
!(ieeeDraft ?? "").endsWith(`-${id.year}`)) {
|
|
1301
|
-
|
|
1328
|
+
// The month face follows the standing month rule (the reference's
|
|
1329
|
+
// to_ieee_format): a numeric month is the glued short form
|
|
1330
|
+
// ("-2018-05", the grammar reads only two digits), a text month
|
|
1331
|
+
// the comma long form (", February 2018", which re-parses through
|
|
1332
|
+
// the joint date clause). Dropping the month published a different
|
|
1333
|
+
// date ("P90003, February 2018 (E)" printed as "P90003-2018").
|
|
1334
|
+
result += /^\d+$/.test(id.month ?? "")
|
|
1335
|
+
? `-${id.year}-${(id.month ?? "").padStart(2, "0")}`
|
|
1336
|
+
: id.month !== undefined
|
|
1337
|
+
? `, ${id.month} ${id.year}`
|
|
1338
|
+
: `-${id.year}`;
|
|
1302
1339
|
}
|
|
1303
1340
|
const marker = id["parenthetical_content"];
|
|
1304
1341
|
if (marker !== undefined)
|
|
@@ -23,6 +23,12 @@ export const iecSchema = define("iec", (s) => {
|
|
|
23
23
|
s.atom("sheet_number", "sheet");
|
|
24
24
|
s.atom("sheet_year", "sheetyear");
|
|
25
25
|
s.atom("vap", "vap", "list");
|
|
26
|
+
s.atom("technical_committee", "tc");
|
|
27
|
+
s.atom("wd_language", "wdlang");
|
|
28
|
+
s.atom("wd_stage", "wdstage");
|
|
29
|
+
s.atom("wp_stage", "wpstage");
|
|
30
|
+
s.atom("wp_type", "wptype");
|
|
31
|
+
s.atom("cispr_identifier", "cispr", "id");
|
|
26
32
|
s.atom("languages.*.code", "lang");
|
|
27
33
|
s.atom("identifiers", "ids", "ids");
|
|
28
34
|
s.facet("languages.*.original_code", "langsrc");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pubid/pubid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"description": "TypeScript PubID identifiers: resolution, normalization and canonical hashes for every identifier in the pubid-testsuite corpus (41 standards bodies), with a conformance runner mirroring the Ruby reference",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"doc:api": "node scripts/api-reference.mjs"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"parsanol": "0.10.
|
|
22
|
+
"parsanol": "0.10.45",
|
|
23
23
|
"yaml": "^2.5.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|