@pubid/pubid 0.1.36 → 0.1.38
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/bipm/index.js +132 -28
- package/dist/flavors/ecma/index.js +35 -1
- package/dist/flavors/iec/grammar.js +39 -2
- package/dist/flavors/iec/index.js +110 -0
- package/dist/flavors/iec/model.js +29 -4
- package/dist/flavors/iho/index.js +126 -5
- package/dist/flavors/iso/grammar.d.ts +5 -0
- package/dist/flavors/iso/grammar.js +68 -1
- package/dist/flavors/iso/index.js +65 -1
- package/dist/flavors/iso/model.js +7 -1
- package/dist/flavors/iso/stages.generated.js +1 -1
- package/dist/flavors/nist/model.js +18 -2
- package/dist/flavors/nist/preprocessor.js +11 -0
- package/dist/flavors/plateau/index.js +21 -2
- package/dist/pubid-urn/schemas/iho.js +3 -0
- package/dist/pubid-urn/schemas/plateau.js +1 -0
- package/package.json +2 -2
- package/vendor/parg/bipm.json +1 -1
- package/vendor/parg/iec.json +1 -1
- package/vendor/parg/iso.json +1 -1
package/artifact/corpus.json.gz
CHANGED
|
Binary file
|
|
@@ -130,15 +130,30 @@ function buildRules() {
|
|
|
130
130
|
.or(str("SI Brochure"))
|
|
131
131
|
.or(str("Brochure sur le SI"))
|
|
132
132
|
.then(space, str("sur le SI").then(space).maybe(), rules["edition"],
|
|
133
|
-
// The version and the
|
|
134
|
-
// language-independent form
|
|
135
|
-
|
|
133
|
+
// The version and the trailing parenthetical are optional (the
|
|
134
|
+
// guide's language-independent form "SI Brochure 9e (2019, E)";
|
|
135
|
+
// the year-less editions "BIPM SI Brochure 9e" and "… (E)" must
|
|
136
|
+
// re-parse as canonically rendered, pubid#694 ruling R1).
|
|
137
|
+
space.then(rules["version"]).maybe(), space
|
|
138
|
+
.then(str("(")
|
|
139
|
+
.then(rules["years"], str(", ").then(lang()).maybe(), str(")"))
|
|
140
|
+
.or(str("(").then(lang(), str(")"))))
|
|
141
|
+
.maybe())
|
|
136
142
|
.as("si_brochure"));
|
|
137
143
|
// The corpus-cited ordinal-edition spelling ("SI Brochure 9th edition
|
|
138
144
|
// (2019)") normalizes the English ordinal onto the canonical "9e".
|
|
139
145
|
rule("si_brochure_ordinal_edition", () => str("SI Brochure")
|
|
140
146
|
.then(space, digits.then(ordinal()).as("ordinal_edition"), space, str("edition"), space.then(rules["year_paren"]).maybe())
|
|
141
147
|
.as("si_brochure_ordinal_edition"));
|
|
148
|
+
// The unmerged si-brochure-id proposal's bare-number edition spellings
|
|
149
|
+
// ("SI Brochure 9", "SI Brochure 9 (E)", "SI Brochure 9 (2024) (F)"):
|
|
150
|
+
// the edition carries no trailing "e", and the year and the language
|
|
151
|
+
// may each sit in their own parentheses (pubid#694 ruling R1). A
|
|
152
|
+
// parse-in alias of the canonical "9e" edition — the builder appends
|
|
153
|
+
// the "e" the proposal drops.
|
|
154
|
+
rule("si_brochure_bare_edition", () => str("SI Brochure")
|
|
155
|
+
.then(space, digits.as("bare_edition"), space.then(str("("), rules["years"], str(")")).maybe(), space.then(str("("), lang(), str(")")).maybe())
|
|
156
|
+
.as("si_brochure_bare_edition"));
|
|
142
157
|
rule("brochure_variant", () => str("Appendix")
|
|
143
158
|
.then(space, digits)
|
|
144
159
|
.or(str("Concise"), str("FAQ"))
|
|
@@ -167,7 +182,7 @@ function buildRules() {
|
|
|
167
182
|
.or(guideBody())
|
|
168
183
|
.as("guide"));
|
|
169
184
|
rule("group_leading", () => rules["meeting_en"].or(rules["meeting_fr"], rules["committee_short"], rules["committee_long_en"], rules["committee_group_fr"], rules["committee_bare"]));
|
|
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"]));
|
|
185
|
+
rule("identifier", () => rules["metrologia"].or(rules["si_brochure"], rules["si_brochure_variant"], rules["si_brochure_section"], rules["si_brochure_ordinal_edition"], rules["si_brochure_bare_edition"], rules["mep"], rules["guide"], rules["committee_long_fr"], rules["committee_prefixed"], rules["group_leading"]));
|
|
171
186
|
rule("root", () => rules["identifier"]);
|
|
172
187
|
return rules;
|
|
173
188
|
}
|
|
@@ -246,22 +261,29 @@ class BipmUrnGenerator extends BaseUrnGenerator {
|
|
|
246
261
|
generate() {
|
|
247
262
|
const id = this.identifier;
|
|
248
263
|
if (id instanceof BipmCommitteeDocument) {
|
|
264
|
+
// The guide-of-record faces (pubid#694, ruling R2): the v1 version
|
|
265
|
+
// segment leads every family and the type segment spells the English
|
|
266
|
+
// type name lowercased ("decision"), never the REC/RES/DECN code.
|
|
267
|
+
// type_code is nil for the bare MRA form -> empty type segment.
|
|
268
|
+
const typeSegment = id.type_code === undefined
|
|
269
|
+
? ""
|
|
270
|
+
: (TYPE_NAME_EN[id.type_code] ?? id.type_code).toLowerCase();
|
|
249
271
|
const parts = [
|
|
250
|
-
"urn", "bipm", id.group?.toLowerCase() ?? "",
|
|
251
|
-
|
|
272
|
+
"urn", "bipm", "v1", id.group?.toLowerCase() ?? "",
|
|
273
|
+
typeSegment, committeeNumberSegment(id),
|
|
252
274
|
];
|
|
253
275
|
if (id.year !== undefined)
|
|
254
276
|
parts.push(String(id.year));
|
|
255
277
|
return parts.join(":");
|
|
256
278
|
}
|
|
257
279
|
if (id instanceof BipmMeeting) {
|
|
258
|
-
const parts = ["urn", "bipm", id.group?.toLowerCase() ?? "", "meeting", id.number ?? ""];
|
|
280
|
+
const parts = ["urn", "bipm", "v1", id.group?.toLowerCase() ?? "", "meeting", id.number ?? ""];
|
|
259
281
|
if (id.year !== undefined)
|
|
260
282
|
parts.push(String(id.year));
|
|
261
283
|
return parts.join(":");
|
|
262
284
|
}
|
|
263
285
|
if (id instanceof BipmMetrologiaArticle) {
|
|
264
|
-
const parts = ["urn", "bipm", "metrologia", String(id.volume())];
|
|
286
|
+
const parts = ["urn", "bipm", "v1", "metrologia", String(id.volume())];
|
|
265
287
|
if (id.issue !== undefined)
|
|
266
288
|
parts.push(id.issue);
|
|
267
289
|
if (id.article !== undefined)
|
|
@@ -269,9 +291,14 @@ class BipmUrnGenerator extends BaseUrnGenerator {
|
|
|
269
291
|
return parts.join(":");
|
|
270
292
|
}
|
|
271
293
|
if (id instanceof BipmSiBrochure) {
|
|
294
|
+
// The guide's edition-number carries no "e" ("9e" emits "9"); the
|
|
295
|
+
// version drops its "v"; the year is the latest of the years range;
|
|
296
|
+
// the language segment is dropped entirely.
|
|
272
297
|
return [
|
|
273
|
-
"urn", "bipm", "
|
|
274
|
-
id.edition ?? ""
|
|
298
|
+
"urn", "bipm", "v1", "bipm", "si-brochure",
|
|
299
|
+
(id.edition ?? "").replace(/e$/i, ""),
|
|
300
|
+
(id.version ?? "").replace(/^v/i, ""),
|
|
301
|
+
(id.years ?? "").split("/").pop() ?? "",
|
|
275
302
|
].join(":");
|
|
276
303
|
}
|
|
277
304
|
throw new ParseFailed(`Cannot build URN for BIPM identifier: ${id.constructor.polymorphicName}`, 0);
|
|
@@ -284,6 +311,14 @@ class BipmCommitteeDocument extends BipmIdentifier {
|
|
|
284
311
|
if (this.form === "long") {
|
|
285
312
|
if (this.language === "F") {
|
|
286
313
|
const name = TYPE_NAME_FR[this.type_code ?? ""] ?? "";
|
|
314
|
+
// The guide's body-prefixed French pattern ("Décision CIPM/111-10
|
|
315
|
+
// (2022)") is the canonical long face (pubid#694, ruling R3): the
|
|
316
|
+
// group qualifies the resource-specific id with a slash. A
|
|
317
|
+
// number-less document keeps the connective spelling the guide's
|
|
318
|
+
// general French pattern uses ("Statement de la CGPM (1889)").
|
|
319
|
+
if (this.number !== undefined) {
|
|
320
|
+
return `${name} ${this.group}/${this.number}${this.yearSegment()}`;
|
|
321
|
+
}
|
|
287
322
|
return `${name}${this.numberSegment()} ${frenchConnective(this.group ?? "")} ${this.group}${this.yearSegment()}`;
|
|
288
323
|
}
|
|
289
324
|
const name = TYPE_NAME_EN[this.type_code ?? ""] ?? "";
|
|
@@ -343,10 +378,18 @@ class BipmSiBrochure extends BipmIdentifier {
|
|
|
343
378
|
const part = this.part === undefined ? "" : ` Part ${this.part}`;
|
|
344
379
|
return `BIPM SI Brochure${part}`;
|
|
345
380
|
}
|
|
381
|
+
// The parens carry the year, the language or both, and stay off
|
|
382
|
+
// entirely when the reference names neither ("SI Brochure 9e", the
|
|
383
|
+
// bare edition without a year — pubid#694's renderer fix).
|
|
346
384
|
const phrase = this.language === "F" ? "sur le SI " : "";
|
|
347
385
|
const lang = this.language === undefined ? "" : `, ${this.language}`;
|
|
348
386
|
const version = this.version === undefined ? "" : ` ${this.version}`;
|
|
349
|
-
|
|
387
|
+
const tail = this.years !== undefined ? `${this.years}${lang}` : (this.language ?? "");
|
|
388
|
+
let result = `BIPM SI Brochure ${phrase}${this.edition}${version}`;
|
|
389
|
+
if (this.years !== undefined || this.language !== undefined) {
|
|
390
|
+
result += ` (${tail})`;
|
|
391
|
+
}
|
|
392
|
+
return result;
|
|
350
393
|
}
|
|
351
394
|
}
|
|
352
395
|
registerType(BipmSiBrochure);
|
|
@@ -446,6 +489,23 @@ class BipmBuilder {
|
|
|
446
489
|
language: normalizeLanguage(s(n["language"])),
|
|
447
490
|
});
|
|
448
491
|
}
|
|
492
|
+
if (tree["si_brochure_bare_edition"] !== undefined) {
|
|
493
|
+
// The proposal drops the trailing "e"; the builder appends it back
|
|
494
|
+
// so the bare-number spellings are parse-in aliases of the canonical
|
|
495
|
+
// "9e" edition (pubid#694 ruling R1). The year and the language sit
|
|
496
|
+
// in separate parentheses; an absent year stays undefined (a partial
|
|
497
|
+
// reference, like "SI Brochure 9th edition" without a year).
|
|
498
|
+
const n = node("si_brochure_bare_edition");
|
|
499
|
+
const edition = `${s(n["bare_edition"])}e`;
|
|
500
|
+
const year = s(n["year"]);
|
|
501
|
+
const language = normalizeLanguage(s(n["language"]));
|
|
502
|
+
return new BipmSiBrochure({
|
|
503
|
+
number: edition,
|
|
504
|
+
edition,
|
|
505
|
+
...(year !== undefined ? { years: year } : {}),
|
|
506
|
+
...(language !== undefined ? { language } : {}),
|
|
507
|
+
});
|
|
508
|
+
}
|
|
449
509
|
if (tree["mep"] !== undefined) {
|
|
450
510
|
const n = node("mep");
|
|
451
511
|
const mepCode = s(n["mep_code"]);
|
|
@@ -500,40 +560,84 @@ export function bipmGrammarImplementation() {
|
|
|
500
560
|
// Inverse of BipmUrnGenerator (lib/pubid/bipm/urn_parser.rb): rebuild
|
|
501
561
|
// the identifier DIRECTLY from the URN fields — the printed forms carry
|
|
502
562
|
// surface detail (meeting ordinals, French wording) the URN omits.
|
|
563
|
+
//
|
|
564
|
+
// Two URN generations parse (pubid#694): the canonical face leads with
|
|
565
|
+
// the guide's `v1` version segment; the pre-adoption face this flavor
|
|
566
|
+
// emitted for years stays parseable as a documented parse-in alias of
|
|
567
|
+
// the same identity. The EMITTER is the new shape; an alias never
|
|
568
|
+
// renders back as itself, it rebuilds the identifier whose toUrn is
|
|
569
|
+
// the v1 face.
|
|
503
570
|
parseUrn(urn) {
|
|
504
571
|
const body = urn.replace(/^urn:bipm:/, "");
|
|
505
572
|
// Keep trailing empty segments (gem splits with -1).
|
|
506
573
|
const parts = body.split(":");
|
|
507
574
|
const presence = (v) => v === undefined || v === "" ? undefined : v;
|
|
508
|
-
|
|
575
|
+
const canonical = parts[0] === "v1";
|
|
576
|
+
const p = canonical ? parts.slice(1) : parts;
|
|
577
|
+
if (p[0] === "metrologia") {
|
|
509
578
|
return new BipmMetrologiaArticle({
|
|
510
|
-
number: presence(
|
|
511
|
-
issue:
|
|
512
|
-
article:
|
|
579
|
+
number: presence(p[1]),
|
|
580
|
+
issue: p[2],
|
|
581
|
+
article: p[3],
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (canonical && p[0] === "bipm") {
|
|
585
|
+
// `bipm:si-brochure:<edition-number>:<version-number>:<year>` —
|
|
586
|
+
// the guide strips the "e" off the edition and the "v" off the
|
|
587
|
+
// version and drops the language segment, so they are restored
|
|
588
|
+
// here; the year stores back verbatim (idempotent emission).
|
|
589
|
+
const edition = presence(p[2]);
|
|
590
|
+
const version = presence(p[3]);
|
|
591
|
+
return new BipmSiBrochure({
|
|
592
|
+
number: edition ? `${edition}e` : undefined,
|
|
593
|
+
edition: edition ? `${edition}e` : undefined,
|
|
594
|
+
version: version ? `v${version}` : undefined,
|
|
595
|
+
years: presence(p[4]),
|
|
513
596
|
});
|
|
514
597
|
}
|
|
515
|
-
if (
|
|
598
|
+
if (p[0] === "si-brochure") {
|
|
599
|
+
// Legacy face: `si-brochure:<lang>:<edition>:<version>:<years>`.
|
|
516
600
|
return new BipmSiBrochure({
|
|
517
|
-
number: presence(
|
|
518
|
-
language: (
|
|
519
|
-
edition:
|
|
520
|
-
version:
|
|
521
|
-
years:
|
|
601
|
+
number: presence(p[2]),
|
|
602
|
+
language: (p[1] ?? "").toUpperCase(),
|
|
603
|
+
edition: p[2],
|
|
604
|
+
version: p[3],
|
|
605
|
+
years: p[4],
|
|
522
606
|
});
|
|
523
607
|
}
|
|
524
|
-
const group = (
|
|
525
|
-
if (
|
|
608
|
+
const group = (p[0] ?? "").toUpperCase();
|
|
609
|
+
if (p[1] === "meeting") {
|
|
526
610
|
return new BipmMeeting({
|
|
527
611
|
group,
|
|
528
|
-
number:
|
|
529
|
-
year:
|
|
612
|
+
number: p[2] ?? "",
|
|
613
|
+
year: p[3] ? Number(p[3]) : undefined,
|
|
530
614
|
});
|
|
531
615
|
}
|
|
616
|
+
// The legacy face carried the lowercase type abbreviation ("res");
|
|
617
|
+
// the v1 face carries the lowercase English type name ("resolution").
|
|
618
|
+
// Both resolve through TYPE_WORD_TO_CODE, so an empty segment (the
|
|
619
|
+
// bare MRA form) stays a nil type_code on either face.
|
|
620
|
+
const segment = presence(p[1]);
|
|
621
|
+
const word = segment === undefined
|
|
622
|
+
? undefined
|
|
623
|
+
: canonical
|
|
624
|
+
? segment.charAt(0).toUpperCase() + segment.slice(1)
|
|
625
|
+
: segment.toUpperCase();
|
|
626
|
+
// Inverse of committeeNumberSegment: split the folded
|
|
627
|
+
// "-a{appendix}[.{annex}]" tail back out of the identifier segment.
|
|
628
|
+
// A bare number is digits and dashes only, so the letter-prefixed
|
|
629
|
+
// tail is unambiguous.
|
|
630
|
+
const raw = p[2] ?? "";
|
|
631
|
+
const folded = /^(.+?)-a(\d+)(?:\.(\d+))?$/.exec(raw);
|
|
632
|
+
const number = folded ? folded[1] : raw;
|
|
532
633
|
return new BipmCommitteeDocument({
|
|
533
634
|
group,
|
|
534
|
-
type_code:
|
|
535
|
-
number: presence(
|
|
536
|
-
year:
|
|
635
|
+
type_code: word === undefined ? undefined : TYPE_WORD_TO_CODE[word],
|
|
636
|
+
number: presence(number),
|
|
637
|
+
year: p[3] ? Number(p[3]) : undefined,
|
|
638
|
+
...(folded
|
|
639
|
+
? { appendix: folded[2], ...(folded[3] !== undefined ? { annex: folded[3] } : {}) }
|
|
640
|
+
: {}),
|
|
537
641
|
});
|
|
538
642
|
},
|
|
539
643
|
parse(input) {
|
|
@@ -106,11 +106,45 @@ class EcmaBuilder extends BaseBuilder {
|
|
|
106
106
|
return value;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The human edition spellings that alias the glued " ed<N>" token
|
|
111
|
+
* (pubid#698; ruby Ecma::Parser.normalize_input), in rewrite order.
|
|
112
|
+
* Each rule rewrites INTO the exact canonical face, so the parse (and
|
|
113
|
+
* therefore the render, the URN and the index key) is the one " ed<N>"
|
|
114
|
+
* already produces, and a canonical face passes through
|
|
115
|
+
* byte-identically.
|
|
116
|
+
*
|
|
117
|
+
* ECMA-262 Edition 5.1 the document cover's own form
|
|
118
|
+
* ECMA-262 ed. 5.1 dotted, spaced
|
|
119
|
+
* ECMA-262 ed 5.1 spaced, undotted
|
|
120
|
+
* ECMA-262 ed.5.1 dotted, unspaced
|
|
121
|
+
* ECMA 262 5.1 bare trailing edition
|
|
122
|
+
*
|
|
123
|
+
* " ed5.1" (no separator) is the grammar's own face and is left alone;
|
|
124
|
+
* a " vol<N>" number is never rewritten — the bare-trailing rule fires
|
|
125
|
+
* only behind a document number's own digit ("ECMA 262 5.1"), so the
|
|
126
|
+
* space-separator form itself ("ECMA 6") keeps its number and the
|
|
127
|
+
* grammar's ed-then-vol order is untouched.
|
|
128
|
+
*/
|
|
129
|
+
const EDITION_ALIASES = [
|
|
130
|
+
// "Edition <N>" starts mid-word, so the replacement carries no space
|
|
131
|
+
// of its own — the space before it is kept as-is.
|
|
132
|
+
[/\bedition[.\s]*(\d+(?:\.\d+)?)\b/gi, "ed$1"],
|
|
133
|
+
// "ed. <N>" / "ed <N>" / "ed.<N>": same mid-word shape. The glued
|
|
134
|
+
// "ed5.1" has no separator after "ed" and cannot match [.\s]+.
|
|
135
|
+
[/\bed[.\s]+(\d+(?:\.\d+)?)\b/g, "ed$1"],
|
|
136
|
+
// Bare trailing edition: fires only when a document number is already
|
|
137
|
+
// present (a digit directly before the space — "ECMA 262 5.1"). A
|
|
138
|
+
// glued "vol2" has no space, and a spaced "vol 2" has no digit before
|
|
139
|
+
// the space, so a volume number is never rewritten.
|
|
140
|
+
[/(\d) (\d+(?:\.\d+)?)$/, "$1 ed$2"],
|
|
141
|
+
];
|
|
109
142
|
export function ecmaGrammarImplementation() {
|
|
110
143
|
const builder = new EcmaBuilder();
|
|
111
144
|
return {
|
|
112
145
|
parse(input) {
|
|
113
|
-
const
|
|
146
|
+
const normalized = EDITION_ALIASES.reduce((acc, [pattern, replacement]) => acc.replace(pattern, replacement), input);
|
|
147
|
+
const tree = parseGrammar(ecmaGrammar, normalized);
|
|
114
148
|
if (typeof tree !== "object" || tree === null || Array.isArray(tree)) {
|
|
115
149
|
throw new ParseFailed("ECMA: unexpected parse tree", 0);
|
|
116
150
|
}
|
|
@@ -86,8 +86,9 @@ function buildRules() {
|
|
|
86
86
|
// --- supplements ---
|
|
87
87
|
const SUPP_ABBRS = [
|
|
88
88
|
"PRF Amd", "PWI Amd", "NP Amd", "ANW Amd", "WD Amd",
|
|
89
|
-
"PRF Cor", "PWI Cor", "NP Cor", "ANW Cor", "WD Cor", "CDCor", "WDCor",
|
|
90
|
-
"CDAM", "DAM", "FDAM", "
|
|
89
|
+
"PRF Cor", "PWI Cor", "NP Cor", "ANW Cor", "WD Cor", "CDCor", "CDCOR", "WDCor",
|
|
90
|
+
"CDAM", "DAM", "FDAM", "PDAM", "FPDAM", "DCOR", "FDCOR", "AMD", "Amd", "COR", "Cor",
|
|
91
|
+
"CDISH", "DISH", "ISH", "Ish",
|
|
91
92
|
];
|
|
92
93
|
rule("supplement_type_with_stage", () => SUPP_ABBRS.reduce((acc, a) => acc.or(str(a)), str(SUPP_ABBRS[0]))
|
|
93
94
|
.or(str("FRAG"))
|
|
@@ -155,6 +156,15 @@ function buildRules() {
|
|
|
155
156
|
.then(str("/"), rules["supplement_type_with_stage"])
|
|
156
157
|
.then(spaceQ, rules["second_part"], rules["third_part"]));
|
|
157
158
|
rule("supplement_identifier_no_third", () => rules["identifier_copublishers_no_third"].as("base")
|
|
159
|
+
// The [language] slot between the base designation and the
|
|
160
|
+
// supplement token: the renderer prints the BASE document's
|
|
161
|
+
// languages right after its date
|
|
162
|
+
// ("ISO/IEC 19772:2009(en)/DAM1:2019" — the rendered twin of the
|
|
163
|
+
// stored "ISO/IEC 19772:2009/DAM 1:2019(E)" input), and without
|
|
164
|
+
// the slot the canonical face could not re-parse. The capture
|
|
165
|
+
// lands on the base (the builder's supplement branch routes it
|
|
166
|
+
// there, like the supplement's own third_part language).
|
|
167
|
+
.then(rules["language"].maybe())
|
|
158
168
|
.then(str("/"), rules["supplement_type_with_stage"])
|
|
159
169
|
.then(spaceQ, rules["second_part"]));
|
|
160
170
|
rule("supplement_identifier", () => rules["supplement_identifier_no_third"].then(rules["third_part"]));
|
|
@@ -164,9 +174,36 @@ function buildRules() {
|
|
|
164
174
|
rule("standalone_supplement", () => rules["prefix_with_copublishers"]
|
|
165
175
|
.then(str("/"), rules["supplement_type_with_stage"])
|
|
166
176
|
.then(space, rules["second_part"], rules["third_part"]));
|
|
177
|
+
// --- the legacy DAM-family prefix face (the JTC1 ballot spelling;
|
|
178
|
+
// decode-to-modern, owner ruling 2026-11-12) ---
|
|
179
|
+
// The stage token precedes the base designation — "IEC/FDAM 60038-1",
|
|
180
|
+
// "ISO/IEC PDAM 12207", "ISO/IEC PDAM 2 10646:2012",
|
|
181
|
+
// "ISO/IEC FPDAM 9075/1", "ISO/IEC FDAM 1 14496-12:2004". A number
|
|
182
|
+
// between the token and the designation is the SUPPLEMENT number; the
|
|
183
|
+
// designation is the base's and is kept whole (unnumbered
|
|
184
|
+
// DAM/PDAM/FDAM/FPDAM means Amendment 1, and the old slash-part
|
|
185
|
+
// spelling "9075/1" carries the base's part). The date rides with the
|
|
186
|
+
// designation — old notation prints the base's year, and the draft
|
|
187
|
+
// supplement carries none of its own. Longest tokens first: FPDAM
|
|
188
|
+
// before PDAM, FDAM before DAM. The slash-part alternative precedes
|
|
189
|
+
// the dash-part one: the designation body also matches a bare number,
|
|
190
|
+
// so a slash-part input must be claimed first or the "/1" would
|
|
191
|
+
// dangle unclaimed.
|
|
192
|
+
rule("legacy_dam_token", () => str("FPDAM").or(str("PDAM"), str("FDAM"), str("DAM")).as("legacy_stage"));
|
|
193
|
+
// The copublisher captures as its own top-level scalar (the wasm
|
|
194
|
+
// binder scatters array bindings over the sibling list when a bare
|
|
195
|
+
// copublisher node rides a flat shape — the parg uses the same
|
|
196
|
+
// legacy_copublisher_item rule for that).
|
|
197
|
+
rule("legacy_copublisher_item", () => str("/").then(spaceQ, ORGS.reduce((acc, o) => acc.or(str(o)), str(ORGS[0])).as("legacy_copublisher")));
|
|
198
|
+
rule("legacy_base_designation", () => match("[0-9]").repeat(1, 6).as("legacy_base_number")
|
|
199
|
+
.then(str("/"), match("[0-9]").repeat(1, Infinity).as("legacy_base_part"))
|
|
200
|
+
.or(rules["number_with_part"].as("legacy_base_number_with_part")));
|
|
201
|
+
rule("legacy_supplement", () => rules["prefix_sole_publisher"]
|
|
202
|
+
.then(spaceQ, rules["legacy_copublisher_item"].maybe(), spaceQ, str("/").maybe(), rules["legacy_dam_token"], space, match("[0-9]").repeat(1, Infinity).as("legacy_supplement_number").then(space).maybe(), rules["legacy_base_designation"], (spaceQ.then(rules["date"])).maybe(), rules["third_part"]));
|
|
167
203
|
rule("identifier", () => rules["working_programme"]
|
|
168
204
|
.or(rules["technical_group"])
|
|
169
205
|
.or(rules["working_document"])
|
|
206
|
+
.or(rules["legacy_supplement"])
|
|
170
207
|
.or(rules["sheet_supplement_identifier"])
|
|
171
208
|
.or(rules["sheet_identifier"])
|
|
172
209
|
.or(rules["supplement_supplement_identifier"])
|
|
@@ -62,6 +62,20 @@ class IecBuilder {
|
|
|
62
62
|
delete tree["vap_suffix"];
|
|
63
63
|
tree["base"]["vap_suffix"] = vapSuffix;
|
|
64
64
|
}
|
|
65
|
+
// The third_part language rides the BASE document on every
|
|
66
|
+
// attached-supplement layer: the ruby grammar binds the layers'
|
|
67
|
+
// language unprefixed, the supplement field set keeps none
|
|
68
|
+
// (SUPP_KEYS), and the wire carries base.languages — the ruby
|
|
69
|
+
// render prints it inside the base portion
|
|
70
|
+
// ("ISO/IEC 19772:2009(en)/DAM1:2019"). Push it down before
|
|
71
|
+
// dispatching; the recursion re-applies it until it reaches the
|
|
72
|
+
// base. A standalone supplement (no base tree) keeps its own
|
|
73
|
+
// languages, like ruby's standalone slice.
|
|
74
|
+
const languages = tree["languages"];
|
|
75
|
+
if (languages !== undefined && isObj(tree["base"])) {
|
|
76
|
+
delete tree["languages"];
|
|
77
|
+
tree["base"]["languages"] = languages;
|
|
78
|
+
}
|
|
65
79
|
// --- working programme / working document ---
|
|
66
80
|
if (tree["wp_stage"] !== undefined)
|
|
67
81
|
return this.buildWorkingProgramme(tree);
|
|
@@ -71,6 +85,10 @@ class IecBuilder {
|
|
|
71
85
|
if (tree["technical_committee"] !== undefined) {
|
|
72
86
|
throw new ParseFailed("IEC: technical groups are not corpus-covered", 0);
|
|
73
87
|
}
|
|
88
|
+
// --- the legacy DAM-family prefix face (decode-to-modern) ---
|
|
89
|
+
if (tree["legacy_stage"] !== undefined) {
|
|
90
|
+
return this.buildLegacySupplement(tree);
|
|
91
|
+
}
|
|
74
92
|
// --- fragment of an amendment/corrigendum (third_part notation) ---
|
|
75
93
|
if (tree["fragment_type"] !== undefined && tree["base"] !== undefined) {
|
|
76
94
|
return this.buildFragment(tree);
|
|
@@ -153,6 +171,41 @@ class IecBuilder {
|
|
|
153
171
|
kind: kind,
|
|
154
172
|
attrs,
|
|
155
173
|
};
|
|
174
|
+
// The ensure_base decode (ruby SupplementIdentifier#ensure_base;
|
|
175
|
+
// the 2026-11-12 owner ruling reversing #693): a standalone draft
|
|
176
|
+
// face whose designation is designation-shaped ("IEC/DCOR 60038-1",
|
|
177
|
+
// "IEC CDISH 60050-191") moves the designation WHOLE onto a
|
|
178
|
+
// concrete InternationalStandard base — number, part, subpart and
|
|
179
|
+
// the date (old notation prints the base's year) — while the
|
|
180
|
+
// supplement keeps a number of its own, defaulting to 1 (the
|
|
181
|
+
// no-number rule: an unnumbered DCOR/DISH/CDISH means
|
|
182
|
+
// Corrigendum/Interpretation-Sheet 1). The concrete base is what
|
|
183
|
+
// makes the decode hash-match (and equal) the modern on-base parse
|
|
184
|
+
// of the same document. A supplement with no part (a true
|
|
185
|
+
// standalone like "IEC/AMD 1:2017") keeps its own number below.
|
|
186
|
+
if ((kind === "amd" || kind === "cor" || kind === "ish") &&
|
|
187
|
+
tree["base"] === undefined &&
|
|
188
|
+
attrs["part"] !== undefined) {
|
|
189
|
+
const baseAttrs = {};
|
|
190
|
+
for (const key of ["publisher", "copublishers", "number", "part", "subpart", "date"]) {
|
|
191
|
+
if (attrs[key] !== undefined)
|
|
192
|
+
baseAttrs[key] = attrs[key];
|
|
193
|
+
}
|
|
194
|
+
const base = new IEC_CLASSES.KIND_CLASSES["is"](baseAttrs);
|
|
195
|
+
const suppAttrs = { base, number: "1" };
|
|
196
|
+
// Supplements copy the delegated publisher/copublishers level
|
|
197
|
+
// (ruby reads them through the base; the stored wire copies the
|
|
198
|
+
// non-default level).
|
|
199
|
+
if (attrs["publisher"] !== undefined && attrs["publisher"] !== "IEC") {
|
|
200
|
+
suppAttrs["publisher"] = attrs["publisher"];
|
|
201
|
+
}
|
|
202
|
+
if (Array.isArray(attrs["copublishers"]) && attrs["copublishers"].length > 0) {
|
|
203
|
+
suppAttrs["copublishers"] = attrs["copublishers"];
|
|
204
|
+
}
|
|
205
|
+
const supplement = this.construct({ kind: kind, attrs: suppAttrs, typedStage }, suppAttrs);
|
|
206
|
+
supplement["base"] = base;
|
|
207
|
+
return supplement;
|
|
208
|
+
}
|
|
156
209
|
// Consolidated supplements (+AMD…+AMD…)
|
|
157
210
|
const consolidated = tree["consolidated_supplements"];
|
|
158
211
|
let identifier = this.construct(parsed, attrs);
|
|
@@ -219,6 +272,63 @@ class IecBuilder {
|
|
|
219
272
|
...(edition !== undefined ? { edition } : {}),
|
|
220
273
|
});
|
|
221
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* The legacy DAM-family prefix face (IEC/FDAM 60038-1, ISO/IEC PDAM
|
|
277
|
+
* 2 10646:2012, ISO/IEC FPDAM 9075/1): the JTC1 ballot spelling puts
|
|
278
|
+
* the stage token before the base designation. Decode onto the
|
|
279
|
+
* modern identity (owner ruling 2026-11-12): the designation is the
|
|
280
|
+
* BASE's, kept whole — the old slash-part spelling "9075/1" is base
|
|
281
|
+
* 9075 part 1 — and the supplement's number is the captured N,
|
|
282
|
+
* defaulting to 1 (the no-number rule). The date rides with the
|
|
283
|
+
* designation (old notation prints the base's year); a draft
|
|
284
|
+
* supplement carries none of its own. The base builds through the
|
|
285
|
+
* ordinary single-document path, so it is the same
|
|
286
|
+
* InternationalStandard the modern on-base parse holds and
|
|
287
|
+
* parse(legacy) == parse(modern).
|
|
288
|
+
*/
|
|
289
|
+
buildLegacySupplement(tree) {
|
|
290
|
+
const baseTree = {};
|
|
291
|
+
if (tree["publisher"] !== undefined)
|
|
292
|
+
baseTree["publisher"] = tree["publisher"];
|
|
293
|
+
if (tree["legacy_copublisher"] !== undefined) {
|
|
294
|
+
baseTree["copublishers"] = [s(tree["legacy_copublisher"])];
|
|
295
|
+
}
|
|
296
|
+
if (tree["legacy_base_number"] !== undefined) {
|
|
297
|
+
// The slash-part designation ("9075/1") captured number and part
|
|
298
|
+
// separately; they go on straight as the plain strings the
|
|
299
|
+
// retyped attributes are.
|
|
300
|
+
baseTree["number_with_part"] = {
|
|
301
|
+
number: s(tree["legacy_base_number"]),
|
|
302
|
+
...(s(tree["legacy_base_part"]) !== undefined ? { part: s(tree["legacy_base_part"]) } : {}),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
else if (tree["legacy_base_number_with_part"] !== undefined) {
|
|
306
|
+
const designation = tree["legacy_base_number_with_part"];
|
|
307
|
+
baseTree["number_with_part"] = isObj(designation)
|
|
308
|
+
? flatten(designation)["number_with_part"]
|
|
309
|
+
: designation;
|
|
310
|
+
}
|
|
311
|
+
if (tree["date"] !== undefined)
|
|
312
|
+
baseTree["date"] = tree["date"];
|
|
313
|
+
if (tree["undated_marker"] !== undefined)
|
|
314
|
+
baseTree["undated_marker"] = tree["undated_marker"];
|
|
315
|
+
if (tree["edition"] !== undefined)
|
|
316
|
+
baseTree["edition"] = tree["edition"];
|
|
317
|
+
if (tree["languages"] !== undefined)
|
|
318
|
+
baseTree["languages"] = tree["languages"];
|
|
319
|
+
// The wrap: one amendment/corrigendum layer over the whole base;
|
|
320
|
+
// third_part's fragment notation rides the outermost layer.
|
|
321
|
+
const suppTree = {
|
|
322
|
+
base: baseTree,
|
|
323
|
+
type_with_stage: s(tree["legacy_stage"]),
|
|
324
|
+
number_with_part: { number: s(tree["legacy_supplement_number"]) ?? "1" },
|
|
325
|
+
};
|
|
326
|
+
if (tree["fragment_type"] !== undefined) {
|
|
327
|
+
suppTree["fragment_type"] = tree["fragment_type"];
|
|
328
|
+
suppTree["fragment_number"] = tree["fragment_number"];
|
|
329
|
+
}
|
|
330
|
+
return this.buildInner(suppTree);
|
|
331
|
+
}
|
|
222
332
|
buildSupplement(tree) {
|
|
223
333
|
const base = this.buildInner(tree["base"]);
|
|
224
334
|
const stageAbbr = s(tree["type_with_stage"]) ?? "";
|
|
@@ -44,8 +44,20 @@ export const TYPED_STAGES = [
|
|
|
44
44
|
TS("ndts", "ndts", "ts", "NDTS"),
|
|
45
45
|
TS("dpas", "dpas", "pas", "DPAS"),
|
|
46
46
|
TS("cdpas", "cdpas", "pas", "CDPAS"),
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// The DAM family's ballot stages carry the CIRCULATION slots as the
|
|
48
|
+
// lead harmonized code (30.20/50.20), not ISO's 30.00/50.00 — CDAM/
|
|
49
|
+
// FDAM keep those (type, leading code) URN keys (gem Amendment
|
|
50
|
+
// registry; the codes are the per-typed-stage keys the wire
|
|
51
|
+
// serializes: pdam, damd, fdamd, fpdam).
|
|
52
|
+
TS("pdam", "cd", "amd", "PDAM", "30.20"),
|
|
53
|
+
TS("damd", "damd", "amd", "DAM", "40.00"),
|
|
54
|
+
TS("fdamd", "fdamd", "amd", "FDAM", "50.00", ["FDAM", "PRF Amd"]),
|
|
55
|
+
TS("fpdam", "fdamd", "amd", "FPDAM", "50.20"),
|
|
56
|
+
// "CDCOR" is the all-caps spelling the attached-supplement renderer
|
|
57
|
+
// emits (abbr.first upcase) when a legacy prefix face decodes onto a
|
|
58
|
+
// CDCor draft; appended as a token so the canonical render re-parses
|
|
59
|
+
// (spelling-only: abbr stays "CDCor").
|
|
60
|
+
TS("cdcor", "cd", "cor", "CDCor", "30.00", ["CDCor", "CDCOR"]),
|
|
49
61
|
TS("dcor", "dcor", "cor", "DCOR"),
|
|
50
62
|
TS("fdcor", "fdcor", "cor", "FDCOR"),
|
|
51
63
|
TS("cdish", "cdish", "ish", "CDISH"),
|
|
@@ -311,6 +323,10 @@ function iecSupplementClass(kind) {
|
|
|
311
323
|
static attributes = extendAttributes(BaseIdentifier, {
|
|
312
324
|
base: { type: IecIdentifier },
|
|
313
325
|
number: { type: "string" },
|
|
326
|
+
// The draft stage rides the wire as the typed stage's code
|
|
327
|
+
// ("stage: fdamd") — the single classes declare the same attr
|
|
328
|
+
// (SINGLE_ATTRS.stage); construct sets it on the parse path.
|
|
329
|
+
stage: { type: "string" },
|
|
314
330
|
synthetic_base: { type: "boolean", default: false },
|
|
315
331
|
});
|
|
316
332
|
static mappings = keyValue({ wire: "number", to: "number" }, { 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 }, { wire: "edition", to: "edition" }, {
|
|
@@ -326,7 +342,11 @@ function iecSupplementClass(kind) {
|
|
|
326
342
|
return list !== undefined && list.length > 0 ? list : undefined;
|
|
327
343
|
},
|
|
328
344
|
fromWire: (h) => (Array.isArray(h["copublishers"]) ? h["copublishers"].map(String) : undefined),
|
|
329
|
-
},
|
|
345
|
+
},
|
|
346
|
+
// The draft stage rides the wire as the typed stage's code
|
|
347
|
+
// ("stage: fdamd"); the single classes map the same key (ruby
|
|
348
|
+
// serializes typed_stage.code and restores it in from_hash).
|
|
349
|
+
{ wire: "stage", to: "stage" }, { wire: "base", to: "base" });
|
|
330
350
|
render() {
|
|
331
351
|
const base = this.base;
|
|
332
352
|
if (base === undefined || this.synthetic_base === true) {
|
|
@@ -340,7 +360,12 @@ function iecSupplementClass(kind) {
|
|
|
340
360
|
return result;
|
|
341
361
|
}
|
|
342
362
|
let result = base.render();
|
|
343
|
-
|
|
363
|
+
// The attached-supplement renderer prints the typed stage's own
|
|
364
|
+
// abbreviation, upcased (ruby renderer.rb: abbr.first.upcase) —
|
|
365
|
+
// a published Amendment renders "/AMD1", a draft stage its own
|
|
366
|
+
// token ("/FDAM1", "/DCOR1", "/CDCOR1" from the "CDCor" entry's
|
|
367
|
+
// spelling-only upcase).
|
|
368
|
+
result += `/${(this.typedStage?.abbr ?? abbr).toUpperCase()}${this.number ?? ""}`;
|
|
344
369
|
if (this.date !== undefined)
|
|
345
370
|
result += `:${this.date.render()}`;
|
|
346
371
|
if (this.edition !== undefined)
|