@pubid/pubid 0.1.21 → 0.1.22
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.
|
@@ -2,6 +2,7 @@ import { ParseFailed, parseGrammar } from "../../grammar/engine.js";
|
|
|
2
2
|
import { PubidDate, Language, Iteration } from "../../model/component.js";
|
|
3
3
|
import { isoGrammar } from "./grammar.js";
|
|
4
4
|
import { IsoBundledIdentifier, IsoDirectives, IsoDirectivesSupplement, IsoEdition, IsoPublisher, IsoSupplementIdentifier, IsoTcDocument, isoClassForType, locateStage, } from "./model.js";
|
|
5
|
+
import { ISO_TYPED_STAGES } from "./stages.generated.js";
|
|
5
6
|
/**
|
|
6
7
|
* Port of lib/pubid/iso/{builder,normalizer}.rb. Class dispatch follows
|
|
7
8
|
* the Ruby Builder: joint → Combined (unreachable by the corpus),
|
|
@@ -246,7 +247,14 @@ class IsoBuilder {
|
|
|
246
247
|
}
|
|
247
248
|
}
|
|
248
249
|
const state = typeStateFromToken(s(tree["type_with_stage"]), "is");
|
|
249
|
-
|
|
250
|
+
// The explicit URN type token pins the class: a stage's abbreviation
|
|
251
|
+
// can overwrite type_with_stage with an IS-only stage ("WDA" for
|
|
252
|
+
// 90.93), and the document must stay a TR/TS (the Ruby builder reads
|
|
253
|
+
// the same document_type hint).
|
|
254
|
+
const documentType = s(tree["document_type"]);
|
|
255
|
+
const typeKey = documentType !== undefined
|
|
256
|
+
? documentType.toLowerCase()
|
|
257
|
+
: state?.entry.typeCode ?? "is";
|
|
250
258
|
if (typeKey === "dir-sup")
|
|
251
259
|
return this.buildDirectivesSupplement(tree, state);
|
|
252
260
|
if (typeKey === "dir")
|
|
@@ -334,8 +342,264 @@ function parseHuman(input) {
|
|
|
334
342
|
}
|
|
335
343
|
return new IsoBuilder().build(tree);
|
|
336
344
|
}
|
|
345
|
+
// --- URN ingestion (lib/pubid/iso/urn_parser.rb) ----------------------------
|
|
346
|
+
// RFC 5141-bis typed-stage abbreviations → stage codes. "DIS"/"ADIS" are
|
|
347
|
+
// ISO ladder stages the reference maps; everything else mirrors the gem's
|
|
348
|
+
// TYPED_STAGE_REVERSE_MAP verbatim.
|
|
349
|
+
const URN_STAGE_ABBRS = {
|
|
350
|
+
WD: "wd", WDS: "wds", CD: "cd", CDV: "cdv", DIS: "dis", FDIS: "fdis",
|
|
351
|
+
PDAM: "pdam", DAM: "dam", FDAM: "fdamd", DCOR: "dcor", FDCOR: "fdcor",
|
|
352
|
+
CDTS: "cdts", DTS: "dts", FDTS: "fdts",
|
|
353
|
+
};
|
|
354
|
+
const URN_TYPE_TOKENS = new Set([
|
|
355
|
+
"tr", "ts", "pas", "guide", "dir", "dir-sup", "iwa-sup", "isp", "iwa", "r", "data",
|
|
356
|
+
]);
|
|
357
|
+
const URN_SUPP_TYPES = new Set(["amd", "cor", "sup", "add"]);
|
|
358
|
+
// The URN type tokens are the gem's type keys; the ts leaf for ISO/R is
|
|
359
|
+
// keyed "rec".
|
|
360
|
+
const URN_TYPE_KEYS = { r: "rec" };
|
|
361
|
+
function urnStageByHarmonized(code) {
|
|
362
|
+
const candidates = ISO_TYPED_STAGES.filter((s) => s.harmonized?.includes(code));
|
|
363
|
+
// The gem prefers the IS family when a code is shared across families
|
|
364
|
+
// (pubid#570 — the registry order is load-order dependent there).
|
|
365
|
+
return candidates.find((s) => s.typeCode === "is") ?? candidates[0];
|
|
366
|
+
}
|
|
367
|
+
// When the URN gives a harmonized code and no type token, the document is
|
|
368
|
+
// an IS: prefer the non-published IS stages (PRF over the published IS for
|
|
369
|
+
// 60.00), then the stage covering the most codes (WD over WDS).
|
|
370
|
+
function urnIsStageByHarmonized(code) {
|
|
371
|
+
const candidates = ISO_TYPED_STAGES.filter((s) => s.typeCode === "is" && s.harmonized?.includes(code));
|
|
372
|
+
if (candidates.length === 0)
|
|
373
|
+
return undefined;
|
|
374
|
+
const nonPublished = candidates.filter((s) => s.stageCode !== "published");
|
|
375
|
+
const pool = nonPublished.length > 0 ? nonPublished : candidates;
|
|
376
|
+
let best = pool[0];
|
|
377
|
+
for (const candidate of pool) {
|
|
378
|
+
if ((candidate.harmonized?.length ?? 0) > (best.harmonized?.length ?? 0))
|
|
379
|
+
best = candidate;
|
|
380
|
+
}
|
|
381
|
+
return best;
|
|
382
|
+
}
|
|
383
|
+
// The URN carries parts as "10164:-5-1-1" or "9001-1"; split into
|
|
384
|
+
// number / part / subpart (a subpart keeps its dashes).
|
|
385
|
+
function parseUrnNumberPart(raw) {
|
|
386
|
+
if (raw === undefined)
|
|
387
|
+
return [undefined, undefined, undefined];
|
|
388
|
+
if (/^\w+:-/.test(raw)) {
|
|
389
|
+
const segments = raw.split(":-");
|
|
390
|
+
const tail = segments.slice(1).join("-").split("-");
|
|
391
|
+
return [
|
|
392
|
+
segments[0],
|
|
393
|
+
tail[0],
|
|
394
|
+
tail.length > 1 ? tail.slice(1).join("-") : undefined,
|
|
395
|
+
];
|
|
396
|
+
}
|
|
397
|
+
if (raw.includes("-")) {
|
|
398
|
+
const segments = raw.split("-");
|
|
399
|
+
return [
|
|
400
|
+
segments[0],
|
|
401
|
+
segments[1],
|
|
402
|
+
segments.length > 2 ? segments.slice(2).join("-") : undefined,
|
|
403
|
+
];
|
|
404
|
+
}
|
|
405
|
+
return [raw, undefined, undefined];
|
|
406
|
+
}
|
|
407
|
+
function parseIsoUrn(urn) {
|
|
408
|
+
if (!urn.startsWith("urn:iso:std:"))
|
|
409
|
+
throw new Error(`Invalid ISO URN: ${urn}`);
|
|
410
|
+
let parts = urn.slice("urn:iso:std:".length).split(":");
|
|
411
|
+
// A trailing "ser" marks an all-parts identifier.
|
|
412
|
+
let allParts = false;
|
|
413
|
+
if (parts[parts.length - 1] === "ser") {
|
|
414
|
+
parts = parts.slice(0, -1);
|
|
415
|
+
allParts = true;
|
|
416
|
+
}
|
|
417
|
+
const publishers = (parts.shift() ?? "").split("-").map((p) => p.toUpperCase());
|
|
418
|
+
let typeCode;
|
|
419
|
+
if (parts[0] !== undefined && URN_TYPE_TOKENS.has(parts[0].toLowerCase())) {
|
|
420
|
+
typeCode = parts.shift().toLowerCase();
|
|
421
|
+
}
|
|
422
|
+
let [number, part, subpart] = parseUrnNumberPart(parts.shift());
|
|
423
|
+
let year;
|
|
424
|
+
if (/^\d{4}$/.test(parts[0] ?? ""))
|
|
425
|
+
year = parts.shift();
|
|
426
|
+
// URN-style part notation can trail the year (":10164:2019:-22").
|
|
427
|
+
if ((parts[0] ?? "").startsWith("-")) {
|
|
428
|
+
const partStr = parts.shift();
|
|
429
|
+
[number, part, subpart] = parseUrnNumberPart(`${number}${partStr}`);
|
|
430
|
+
}
|
|
431
|
+
if (year === undefined && /^\d{4}$/.test(parts[0] ?? ""))
|
|
432
|
+
year = parts.shift();
|
|
433
|
+
let edition;
|
|
434
|
+
if ((parts[0] ?? "").startsWith("ed-"))
|
|
435
|
+
edition = parts.shift().slice(3);
|
|
436
|
+
let languages;
|
|
437
|
+
if (parts[0] !== undefined && !/^\d+$/.test(parts[0]) &&
|
|
438
|
+
!/^(amd|cor|sup|add|v\d+|stage-|ed-)/i.test(parts[0]) &&
|
|
439
|
+
!(parts[0].toUpperCase() in URN_STAGE_ABBRS) &&
|
|
440
|
+
!/^[A-Za-z]+\.\d+$/.test(parts[0])) {
|
|
441
|
+
languages = parts.shift().split(",").map((l) => l.toUpperCase()).join("/");
|
|
442
|
+
}
|
|
443
|
+
// Stage: either "stage-XX.XX[.vN]" (harmonized) or a typed abbreviation
|
|
444
|
+
// ("WD", "CD.2").
|
|
445
|
+
let stageCode;
|
|
446
|
+
let stageFromAbbr;
|
|
447
|
+
let stageIteration;
|
|
448
|
+
if ((parts[0] ?? "").startsWith("stage-")) {
|
|
449
|
+
const stageStr = parts.shift();
|
|
450
|
+
stageCode = stageStr.slice(6).replace(/\.v\d+$/i, "");
|
|
451
|
+
const iteration = /\.v(\d+)$/i.exec(stageStr);
|
|
452
|
+
if (iteration !== null)
|
|
453
|
+
stageIteration = iteration[1];
|
|
454
|
+
}
|
|
455
|
+
else if ((parts[0] !== undefined && parts[0].toUpperCase() in URN_STAGE_ABBRS) ||
|
|
456
|
+
(parts[0] !== undefined && /^[A-Za-z]+\.\d+$/.test(parts[0]) &&
|
|
457
|
+
(parts[0].toUpperCase().split(".")[0] ?? "") in URN_STAGE_ABBRS)) {
|
|
458
|
+
const stageAbbr = parts[0].toUpperCase();
|
|
459
|
+
// A supplement-family stage directly before its own supplement type
|
|
460
|
+
// token ("...:FDAM:amd:1:v1") names the SUPPLEMENT's stage: consuming
|
|
461
|
+
// it as the base's stage builds the base as a base-less supplement,
|
|
462
|
+
// which cannot render (the gem guards the same way).
|
|
463
|
+
const family = locateStage(stageAbbr.split(".")[0] ?? "")?.typeCode;
|
|
464
|
+
const supplementFollows = (family !== undefined &&
|
|
465
|
+
["amd", "cor", "suppl", "add"].includes(family)) &&
|
|
466
|
+
parts.slice(1).some((token) => URN_SUPP_TYPES.has(token.toLowerCase()) &&
|
|
467
|
+
(URN_TYPE_KEYS[token.toLowerCase()] ?? token.toLowerCase()) === family);
|
|
468
|
+
if (!supplementFollows) {
|
|
469
|
+
parts.shift();
|
|
470
|
+
if (stageAbbr.includes(".")) {
|
|
471
|
+
const abbrPart = stageAbbr.split(".")[0];
|
|
472
|
+
const iterationPart = stageAbbr.split(".")[1];
|
|
473
|
+
stageCode = URN_STAGE_ABBRS[abbrPart];
|
|
474
|
+
stageFromAbbr = abbrPart;
|
|
475
|
+
stageIteration = iterationPart;
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
stageCode = URN_STAGE_ABBRS[stageAbbr];
|
|
479
|
+
stageFromAbbr = stageAbbr;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// Supplements (amd, cor, sup, add), left-to-right: each wraps the previous.
|
|
484
|
+
const supplements = [];
|
|
485
|
+
while (parts.length > 0) {
|
|
486
|
+
const supp = {};
|
|
487
|
+
if ((parts[0] ?? "").startsWith("stage-")) {
|
|
488
|
+
supp.stage = parts.shift().slice(6).replace(/\.v\d+$/i, "");
|
|
489
|
+
}
|
|
490
|
+
else if (parts[0] !== undefined && parts[0].toUpperCase() in URN_STAGE_ABBRS) {
|
|
491
|
+
supp.stage = URN_STAGE_ABBRS[parts.shift().toUpperCase()];
|
|
492
|
+
}
|
|
493
|
+
if (parts[0] !== undefined && URN_SUPP_TYPES.has(parts[0].toLowerCase())) {
|
|
494
|
+
supp.type = parts.shift().toLowerCase();
|
|
495
|
+
}
|
|
496
|
+
if (/^\d+$/.test(parts[0] ?? "")) {
|
|
497
|
+
if (/^\d{4}$/.test(parts[0]))
|
|
498
|
+
supp.year = parts.shift();
|
|
499
|
+
else
|
|
500
|
+
supp.number = parts.shift();
|
|
501
|
+
}
|
|
502
|
+
if ((parts[0] ?? "").startsWith("v")) {
|
|
503
|
+
const version = parts.shift().slice(1);
|
|
504
|
+
supp.number = version.split(".")[0];
|
|
505
|
+
}
|
|
506
|
+
if (supp.year === undefined && /^\d{4}$/.test(parts[0] ?? ""))
|
|
507
|
+
supp.year = parts.shift();
|
|
508
|
+
if (parts[0] !== undefined && !/^\d+$/.test(parts[0]) &&
|
|
509
|
+
!/^(amd|cor|sup|add|v\d+|stage-)/i.test(parts[0])) {
|
|
510
|
+
supp.languages = parts.shift().split(",").map((l) => l.toUpperCase()).join("/");
|
|
511
|
+
}
|
|
512
|
+
supplements.push(supp);
|
|
513
|
+
}
|
|
514
|
+
// Builder tree, mirroring the gem's build_identifier.
|
|
515
|
+
const base = {};
|
|
516
|
+
if (publishers[0] !== undefined && publishers[0] !== "") {
|
|
517
|
+
base["publisher"] = publishers[0];
|
|
518
|
+
if (publishers.length > 1) {
|
|
519
|
+
base["copublishers"] = publishers.slice(1).map((c) => ({ copublisher: c }));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (part !== undefined || subpart !== undefined) {
|
|
523
|
+
base["number_with_part"] = [number, part, subpart].filter((p) => p !== undefined).join("-");
|
|
524
|
+
}
|
|
525
|
+
else if (number !== undefined) {
|
|
526
|
+
base["number"] = number;
|
|
527
|
+
}
|
|
528
|
+
if (year !== undefined)
|
|
529
|
+
base["date"] = year;
|
|
530
|
+
if (edition !== undefined)
|
|
531
|
+
base["edition"] = edition;
|
|
532
|
+
if (typeCode !== undefined && typeCode !== "is") {
|
|
533
|
+
base["type_with_stage"] = typeCode.toUpperCase();
|
|
534
|
+
// Pin the document class against the stage block below overwriting
|
|
535
|
+
// type_with_stage (the Ruby builder reads the same hint).
|
|
536
|
+
base["document_type"] = (URN_TYPE_KEYS[typeCode] ?? typeCode).toUpperCase();
|
|
537
|
+
}
|
|
538
|
+
if (stageCode !== undefined) {
|
|
539
|
+
let stage;
|
|
540
|
+
if (stageFromAbbr !== undefined) {
|
|
541
|
+
stage = locateStage(stageFromAbbr) ??
|
|
542
|
+
ISO_TYPED_STAGES.find((s) => s.stageCode === stageCode);
|
|
543
|
+
}
|
|
544
|
+
else if (typeCode === undefined || typeCode === "is") {
|
|
545
|
+
stage = urnIsStageByHarmonized(stageCode);
|
|
546
|
+
// FCD is a legacy spelling of the DIS stage in URN form.
|
|
547
|
+
if (stageCode === "40.00" && stage?.stageCode === "fcd") {
|
|
548
|
+
stage = ISO_TYPED_STAGES.find((s) => s.stageCode === "dis" && s.harmonized?.includes("40.00")) ?? stage;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
stage ??= urnStageByHarmonized(stageCode);
|
|
552
|
+
if (stage !== undefined)
|
|
553
|
+
base["type_with_stage"] = stage.abbr[0] ?? "";
|
|
554
|
+
else
|
|
555
|
+
base["stage"] = stageCode;
|
|
556
|
+
if (stageIteration !== undefined)
|
|
557
|
+
base["stage_iteration"] = stageIteration;
|
|
558
|
+
}
|
|
559
|
+
if (languages !== undefined)
|
|
560
|
+
base["languages"] = languages;
|
|
561
|
+
let tree = base;
|
|
562
|
+
for (const supp of supplements) {
|
|
563
|
+
// A bare language token with no supplement identity names the BASE
|
|
564
|
+
// document (the gem merges it the same way — a wrapper would drop it).
|
|
565
|
+
if (supp.type === undefined && supp.stage === undefined &&
|
|
566
|
+
supp.number === undefined && supp.year === undefined &&
|
|
567
|
+
supp.languages !== undefined) {
|
|
568
|
+
base["languages"] = supp.languages;
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const suppTree = {};
|
|
572
|
+
if (supp.stage !== undefined) {
|
|
573
|
+
const stage = ISO_TYPED_STAGES.find((s) => s.stageCode === supp.stage);
|
|
574
|
+
suppTree["type_with_stage"] = stage !== undefined
|
|
575
|
+
? stage.abbr[0] ?? ""
|
|
576
|
+
: supp.stage.toUpperCase();
|
|
577
|
+
}
|
|
578
|
+
if (supp.type !== undefined && suppTree["type_with_stage"] === undefined) {
|
|
579
|
+
suppTree["type_with_stage"] = supp.type.toUpperCase();
|
|
580
|
+
}
|
|
581
|
+
if (supp.number !== undefined)
|
|
582
|
+
suppTree["number"] = supp.number;
|
|
583
|
+
if (supp.year !== undefined)
|
|
584
|
+
suppTree["date"] = supp.year;
|
|
585
|
+
if (supp.languages !== undefined)
|
|
586
|
+
suppTree["languages"] = supp.languages;
|
|
587
|
+
tree = { base: tree, ...suppTree };
|
|
588
|
+
}
|
|
589
|
+
const identifier = new IsoBuilder().build(tree);
|
|
590
|
+
if (allParts) {
|
|
591
|
+
identifier.all_parts = true;
|
|
592
|
+
}
|
|
593
|
+
return identifier;
|
|
594
|
+
}
|
|
337
595
|
export function isoGrammarImplementation() {
|
|
338
596
|
return {
|
|
597
|
+
// Inverse of the ISO UrnGenerator (lib/pubid/iso/urn_parser.rb): the
|
|
598
|
+
// reference rebuilds a builder hash straight from the positional URN
|
|
599
|
+
// fields, so the port does the same against the shared IsoBuilder.
|
|
600
|
+
parseUrn(urn) {
|
|
601
|
+
return parseIsoUrn(urn);
|
|
602
|
+
},
|
|
339
603
|
parse(input) {
|
|
340
604
|
return parseHuman(input);
|
|
341
605
|
},
|
|
@@ -61,7 +61,7 @@ export declare abstract class IsoIdentifier extends BaseIdentifier {
|
|
|
61
61
|
protected stageString(): string;
|
|
62
62
|
/** Render with an EXPLICIT long-format flag (the supplement
|
|
63
63
|
* renderer propagates its own context to the base). */
|
|
64
|
-
renderWithFormat(formatLong: boolean): string;
|
|
64
|
+
renderWithFormat(formatLong: boolean, withLanguages?: boolean): string;
|
|
65
65
|
/** Ruby SupplementRenderer renders the base with the wrapper's
|
|
66
66
|
* detected context (stage_format_long propagates down the chain;
|
|
67
67
|
* with_date being non-nil pins it against re-detection — a nested
|
|
@@ -119,7 +119,7 @@ export class IsoIdentifier extends BaseIdentifier {
|
|
|
119
119
|
}
|
|
120
120
|
/** Render with an EXPLICIT long-format flag (the supplement
|
|
121
121
|
* renderer propagates its own context to the base). */
|
|
122
|
-
renderWithFormat(formatLong) {
|
|
122
|
+
renderWithFormat(formatLong, withLanguages = true) {
|
|
123
123
|
let result = "";
|
|
124
124
|
if (this.typeKey() === "iwa") {
|
|
125
125
|
const stage = this.typedStageState === undefined ? "" : stageAbbr(this.typedStageState.entry, formatLong);
|
|
@@ -141,7 +141,8 @@ export class IsoIdentifier extends BaseIdentifier {
|
|
|
141
141
|
const num = this.numberPortion(true);
|
|
142
142
|
if (num !== "")
|
|
143
143
|
result += result === "" ? num : ` ${num}`;
|
|
144
|
-
|
|
144
|
+
if (withLanguages)
|
|
145
|
+
result += this.languagePortion(this.languageSingle());
|
|
145
146
|
if (this.all_parts === true)
|
|
146
147
|
result += " (all parts)";
|
|
147
148
|
return result;
|
|
@@ -152,7 +153,11 @@ export class IsoIdentifier extends BaseIdentifier {
|
|
|
152
153
|
* amendment renders "AMD" under a "CD Cor" top but "Amd" under a
|
|
153
154
|
* "Cor." top). */
|
|
154
155
|
renderAsSupplementBase(long) {
|
|
155
|
-
|
|
156
|
+
// The reference re-renders a nested base from the four propagated
|
|
157
|
+
// options alone, and that rebuilt context defaults to
|
|
158
|
+
// with_language_code :none — only the outermost identifier renders
|
|
159
|
+
// its own language portion.
|
|
160
|
+
return this.renderWithFormat(long, false);
|
|
156
161
|
}
|
|
157
162
|
stageStringFor(formatLong) {
|
|
158
163
|
const ts = this.typedStageState;
|
|
@@ -618,12 +623,15 @@ export class IsoSupplementIdentifier extends IsoSingleIdentifier {
|
|
|
618
623
|
});
|
|
619
624
|
static mappings = SUPPLEMENT_MAPPINGS;
|
|
620
625
|
render() {
|
|
621
|
-
return this.renderSupplement(this.stageFormatLong());
|
|
626
|
+
return this.renderSupplement(this.stageFormatLong(), false);
|
|
622
627
|
}
|
|
623
628
|
renderAsSupplementBase(long) {
|
|
624
|
-
return this.renderSupplement(long);
|
|
629
|
+
return this.renderSupplement(long, true);
|
|
625
630
|
}
|
|
626
|
-
|
|
631
|
+
// The reference renders a nested base from a rebuilt context whose
|
|
632
|
+
// with_language_code defaults to :none, so only the outermost
|
|
633
|
+
// identifier of a chain renders its language portion.
|
|
634
|
+
renderSupplement(long, asBase) {
|
|
627
635
|
const base = this.base;
|
|
628
636
|
const baseStr = base === undefined ? "" : base.renderAsSupplementBase(long);
|
|
629
637
|
const ts = this.typedStageState;
|
|
@@ -635,7 +643,8 @@ export class IsoSupplementIdentifier extends IsoSingleIdentifier {
|
|
|
635
643
|
result += " ";
|
|
636
644
|
result += num;
|
|
637
645
|
}
|
|
638
|
-
|
|
646
|
+
if (!asBase)
|
|
647
|
+
result += this.languagePortion(this.languageSingle());
|
|
639
648
|
return result;
|
|
640
649
|
}
|
|
641
650
|
}
|
|
@@ -688,7 +688,10 @@ function nistClass(spec) {
|
|
|
688
688
|
}
|
|
689
689
|
renderMr() {
|
|
690
690
|
if (spec.style === "fips") {
|
|
691
|
-
|
|
691
|
+
// The MR form keeps a parsed publisher ("NIST.FIPS.197-upd1" is the
|
|
692
|
+
// DOI suffix NIST publishes); a bare FIPS invents none.
|
|
693
|
+
const series = this.seriesCode() ?? "";
|
|
694
|
+
let result = this.publisher !== undefined ? `${this.publisher}.${series}` : series;
|
|
692
695
|
if (this.number !== undefined)
|
|
693
696
|
result += `.${this.number}`;
|
|
694
697
|
if (this.part instanceof NistPart)
|
package/dist/urn/parsers.js
CHANGED
|
@@ -650,6 +650,31 @@ export const URN_PARSERS = {
|
|
|
650
650
|
return parse(text);
|
|
651
651
|
},
|
|
652
652
|
},
|
|
653
|
+
// lib/pubid/easc/urn_parser.rb — "urn:easc:<series>[:v]:<number>[:<year>]";
|
|
654
|
+
// the human face renders the Cyrillic series label
|
|
655
|
+
// ("ПМГ 02-2008", "ПМГ В 31-2001", "РМГ 151-2025").
|
|
656
|
+
easc: {
|
|
657
|
+
prefix: "urn:easc:",
|
|
658
|
+
reconstruct(body, parse) {
|
|
659
|
+
const parts = body.split(":");
|
|
660
|
+
const series = parts.shift() ?? "";
|
|
661
|
+
if (series === "") {
|
|
662
|
+
throw new Error(`EASC URN missing series: ${JSON.stringify(body)}`);
|
|
663
|
+
}
|
|
664
|
+
const label = series.toUpperCase() === "PMG" ? "ПМГ" : "РМГ";
|
|
665
|
+
let rest = parts;
|
|
666
|
+
let separator = " ";
|
|
667
|
+
if (/^v$/i.test(rest[0] ?? "")) {
|
|
668
|
+
separator = " В ";
|
|
669
|
+
rest = rest.slice(1);
|
|
670
|
+
}
|
|
671
|
+
const number = rest[0] ?? "";
|
|
672
|
+
let text = `${label}${separator}${number}`;
|
|
673
|
+
if (rest[1] !== undefined)
|
|
674
|
+
text += `-${rest[1]}`;
|
|
675
|
+
return parse(text);
|
|
676
|
+
},
|
|
677
|
+
},
|
|
653
678
|
// lib/pubid/ieee/urn_parser.rb — "IEEE Std <code>[-<year>]". The fixture
|
|
654
679
|
// pins the reference's own parse failures for narrative/artifact rows.
|
|
655
680
|
ieee: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pubid/pubid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
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",
|