@pubid/pubid 0.1.20 → 0.1.21

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.
Files changed (2) hide show
  1. package/dist/urn/parsers.js +156 -50
  2. package/package.json +1 -1
@@ -1,10 +1,36 @@
1
1
  import { splitParts, withYear } from "./types.js";
2
- import { grammarImplementation } from "../flavors/index.js";
3
- import { NationalAdoption } from "../flavors/evs/model.js";
4
2
  // Each parser mirrors lib/pubid/<flavor>/urn_parser.rb#parse_urn exactly —
5
3
  // including its lossiness (dropped segments stay dropped) and its error
6
4
  // cases. The parity fixture (test/fixtures/urn-parity.json) pins the
7
5
  // reference behavior for ~93k corpus URNs.
6
+ // The adopted CEN spine consumes its own supplements; their urn segments
7
+ // carry the consolidation marker ("plus" = "+A1", its absence = "/A1") —
8
+ // cen_cenelec/urn_generator.rb's supplement_segments.
9
+ function adoptedSupplement(parts) {
10
+ let faces = "";
11
+ while (["plus", "slash", "amd", "cor", "frag"].includes(parts[0] ?? "")) {
12
+ let sep = "/";
13
+ let kind;
14
+ if (parts[0] === "plus" || parts[0] === "slash") {
15
+ sep = parts.shift() === "plus" ? "+" : "/";
16
+ kind = parts.shift();
17
+ }
18
+ else {
19
+ kind = parts.shift();
20
+ }
21
+ const num = /^\d{1,3}$/.test(parts[0] ?? "") ? parts.shift() : undefined;
22
+ const yr = /^\d{4}$/.test(parts[0] ?? "") ? parts.shift() : undefined;
23
+ const iter = num ?? "";
24
+ const year = yr !== undefined ? `:${yr}` : "";
25
+ if (kind === "amd")
26
+ faces += `${sep}A${iter}${year}`;
27
+ else if (kind === "cor")
28
+ faces += `${sep}AC${iter}${year}`;
29
+ else
30
+ faces += ` FRAG${iter}`;
31
+ }
32
+ return faces;
33
+ }
8
34
  export const URN_PARSERS = {
9
35
  // lib/pubid/ansi/urn_parser.rb
10
36
  ansi: {
@@ -459,64 +485,111 @@ export const URN_PARSERS = {
459
485
  return parse(text);
460
486
  },
461
487
  },
462
- // lib/pubid/evs/urn_parser.rb — EN [org] number[:year] with an optional
463
- // amd/cor iteration tail; the rebuilt text is parsed as a CEN adoption.
488
+ // lib/pubid/evs/urn_parser.rb — reassemble the identifier's human text
489
+ // (draft prefix, adopted org, national annex, language, supplements)
490
+ // and delegate to the EVS grammar, exactly like the reference.
464
491
  evs: {
465
492
  prefix: "urn:evs:",
466
493
  reconstruct(body, parse) {
467
494
  const parts = splitParts(body);
468
- const type = parts.shift() ?? "";
469
- if (type.toLowerCase() !== "en") {
470
- throw new Error(`unsupported type in ${JSON.stringify(body)}`);
471
- }
495
+ const draft = parts[0] === "pr";
496
+ if (draft)
497
+ parts.shift();
472
498
  const orgs = {
473
499
  iso: "ISO",
474
500
  "iso-iec": "ISO/IEC",
475
501
  iec: "IEC",
476
502
  cispr: "CISPR",
477
503
  };
478
- const orgParts = [];
479
- while (parts[0] !== undefined && /^[a-z][a-z-]*$/.test(parts[0])) {
480
- orgParts.push(parts.shift());
504
+ const types = { ts: "TS", tr: "TR" };
505
+ // The trailing language and the EVS-level national annex peel
506
+ // before the body dispatch — both are EVS-level, never CEN
507
+ // segments.
508
+ let language;
509
+ const last = parts[parts.length - 1];
510
+ if (parts.length > 1 && last !== undefined && /^(et-en|et|en)$/.test(last)) {
511
+ language = parts.pop();
481
512
  }
482
- let text = "EN";
483
- if (orgParts.length) {
484
- const org = orgs[orgParts.join("-")];
485
- if (org === undefined)
486
- throw new Error(`unknown adopted org in ${JSON.stringify(body)}`);
487
- text += ` ${org}`;
513
+ let naPresent = false;
514
+ let naYear;
515
+ const naIndex = parts.indexOf("na");
516
+ if (naIndex >= 0) {
517
+ parts.splice(naIndex, 1);
518
+ naPresent = true;
519
+ if (/^\d{4}$/.test(parts[naIndex] ?? ""))
520
+ naYear = parts.splice(naIndex, 1)[0];
488
521
  }
489
- const number = parts.shift();
490
- if (number === undefined)
491
- throw new Error(`missing number in ${JSON.stringify(body)}`);
492
- text += ` ${number}`;
493
- const year = parts.shift();
494
- if (year)
495
- text += `:${year}`;
496
- if (parts.length) {
497
- const yearTail = parts[parts.length - 1]?.match(/^\d{4}$/) ? parts.pop() : undefined;
498
- const kind = parts.shift();
499
- const iteration = parts.shift();
500
- if (kind !== "amd" && kind !== "cor") {
501
- throw new Error(`malformed supplement in ${JSON.stringify(body)}`);
522
+ const head = parts.shift() ?? "";
523
+ let text = draft ? "prEVS" : "EVS";
524
+ if (/^en$/i.test(head)) {
525
+ text += "-EN";
526
+ const orgParts = [];
527
+ while (parts[0] !== undefined && /^[a-z][a-z-]*$/.test(parts[0])) {
528
+ orgParts.push(parts.shift());
529
+ }
530
+ if (orgParts.length) {
531
+ const org = orgs[orgParts.join("-")];
532
+ if (org === undefined)
533
+ throw new Error(`unknown adopted org in ${JSON.stringify(body)}`);
534
+ text += ` ${org}`;
502
535
  }
503
- if (iteration === undefined || parts.length) {
504
- throw new Error(`malformed supplement in ${JSON.stringify(body)}`);
536
+ const number = parts.shift();
537
+ if (number === undefined)
538
+ throw new Error(`missing number in ${JSON.stringify(body)}`);
539
+ text += ` ${number}`;
540
+ const year = parts.shift();
541
+ if (year !== undefined)
542
+ text += `:${year}`;
543
+ text += adoptedSupplement(parts);
544
+ }
545
+ else if (head === "ts" || head === "tr") {
546
+ const number = parts.shift();
547
+ const year = parts.shift();
548
+ text += `-${types[head]} ${number}`;
549
+ if (year !== undefined)
550
+ text += `:${year}`;
551
+ }
552
+ else if (head === "iso" || head === "iec" || head === "cispr") {
553
+ const org = orgs[head];
554
+ const number = parts.shift();
555
+ let part;
556
+ if (/^\d+$/.test(parts[0] ?? "") && !/^\d{4}$/.test(parts[0]) && parts.length > 1) {
557
+ part = parts.shift();
505
558
  }
506
- text += kind === "amd" ? `/A${iteration}` : `/AC${iteration}`;
507
- if (yearTail)
508
- text += `:${yearTail}`;
559
+ const year = parts.shift();
560
+ text += `-${org} ${number}`;
561
+ if (part !== undefined)
562
+ text += `-${part}`;
563
+ if (year !== undefined)
564
+ text += `:${year}`;
509
565
  }
510
- // The reference constructs NationalAdoption around a parsed CEN
511
- // base (lib/pubid/evs/urn_parser.rb) rather than re-parsing the
512
- // rebuilt text as EVS.
513
- const cen = grammarImplementation("cen_cenelec");
514
- const base = cen.parse(text);
515
- return new NationalAdoption({ base });
566
+ else if (/^\d/.test(head)) {
567
+ const number = head;
568
+ const year = parts.shift();
569
+ text += ` ${number}`;
570
+ if (year !== undefined)
571
+ text += `:${year}`;
572
+ }
573
+ else {
574
+ throw new Error(`unsupported shape in ${JSON.stringify(body)}`);
575
+ }
576
+ if (parts.length)
577
+ throw new Error(`trailing segments in ${JSON.stringify(body)}`);
578
+ if (naPresent) {
579
+ text += "+NA";
580
+ if (naYear !== undefined)
581
+ text += `:${naYear}`;
582
+ }
583
+ if (language !== undefined)
584
+ text += ` ${language}`;
585
+ return parse(text);
516
586
  },
517
587
  },
518
- // lib/pubid/nist/urn_parser.rb — type token upcased via the reference
519
- // map; ".supp" stripped; ".r<N>" revision suffix split off the payload.
588
+ // lib/pubid/nist/urn_parser.rb — the generator dot-joins the identity's
589
+ // fields after the number; reassemble the human spelling the flavor
590
+ // parser reads (edition/volume glue onto the number, a revision glues
591
+ // after them, ".supp" marks supplementability, ".supp-<v>" is "sup<v>",
592
+ // insert/errata glue).
520
593
  nist: {
521
594
  prefix: "urn:nist:",
522
595
  reconstruct(body, parse) {
@@ -530,17 +603,50 @@ export const URN_PARSERS = {
530
603
  const typeToken = parts[0] ?? "";
531
604
  const payload = parts[1] ?? "";
532
605
  const label = typeUpcase[typeToken.toLowerCase()] ?? typeToken.toUpperCase();
533
- const stripped = payload.replace(/\.supp$/, "");
534
- const revMatch = stripped.match(/^(.*)\.(r\d+)$/i);
535
- const code = revMatch ? revMatch[1] : stripped;
536
- const revision = revMatch ? revMatch[2] : undefined;
606
+ const tokens = payload.split(".");
607
+ // Ruby's String#split drops trailing empty fields; JS keeps them, and a
608
+ // trailing "." would become a stray marker that the flavor rejects.
609
+ while (tokens.length > 0 && tokens[tokens.length - 1] === "")
610
+ tokens.pop();
611
+ const code = tokens.shift() ?? "";
612
+ let marker = "";
613
+ let part;
614
+ let edition;
615
+ let volume;
616
+ let revision;
617
+ let supplement;
618
+ for (const token of tokens) {
619
+ const supp = /^supp-(.+)$/is.exec(token);
620
+ if (/^r\d+$/i.test(token))
621
+ revision = token;
622
+ else if (/^e\w+$/i.test(token))
623
+ edition = token;
624
+ else if (/^v\d+$/i.test(token))
625
+ volume = token;
626
+ else if (supp !== null)
627
+ supplement = supp[1];
628
+ else if (token === "supp")
629
+ continue; // supplementability marker
630
+ else if (token === "insert" || token === "errata")
631
+ marker += token;
632
+ else if (/^[A-Z]$/.test(token))
633
+ part = token; // part letter, glued to the code
634
+ else if (/^[A-Z][a-z]+\d{4}$/.test(token)) {
635
+ // the edition's additional text ("13e2.June1908")
636
+ if (edition !== undefined)
637
+ edition = `${edition}.${token}`;
638
+ }
639
+ else
640
+ marker += `.${token}`;
641
+ }
537
642
  // The NBS-era series carry the NBS imprint in their canonical form
538
643
  // ("NBS CSM 1") — the rebuild names the publisher the document
539
644
  // carries, or the flavor parse rejects its own URN's rebuild.
540
645
  const publisher = typeToken.toLowerCase() === "csm" ? "NBS" : "NIST";
541
- let text = `${publisher} ${label} ${code}`;
542
- if (revision)
543
- text += revision;
646
+ let text = `${publisher} ${label} ${code}${part ?? ""}${marker}`;
647
+ text += `${edition ?? ""}${volume ?? ""}${revision ?? ""}`;
648
+ if (supplement !== undefined)
649
+ text += `sup${supplement}`;
544
650
  return parse(text);
545
651
  },
546
652
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubid/pubid",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
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",