@pubid/pubid 0.1.7 → 0.1.9

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 (79) hide show
  1. package/artifact/corpus.json.gz +0 -0
  2. package/dist/flavors/cen_cenelec/grammar.js +10 -4
  3. package/dist/flavors/cen_cenelec/index.js +20 -1
  4. package/dist/flavors/cen_cenelec/model.d.ts +1 -0
  5. package/dist/flavors/cen_cenelec/model.js +4 -1
  6. package/dist/flavors/evs/grammar.js +53 -7
  7. package/dist/flavors/evs/index.js +74 -7
  8. package/dist/flavors/evs/model.d.ts +37 -1
  9. package/dist/flavors/evs/model.js +173 -11
  10. package/dist/flavors/iec/index.js +15 -3
  11. package/dist/flavors/iec/model.d.ts +1 -1
  12. package/dist/flavors/iec/model.js +12 -2
  13. package/dist/flavors/ieee/artifact_tree.d.ts +2 -0
  14. package/dist/flavors/ieee/artifact_tree.js +36 -0
  15. package/dist/flavors/ieee/grammar.js +99 -19
  16. package/dist/flavors/ieee/index.js +312 -18
  17. package/dist/flavors/ieee/model.d.ts +5 -0
  18. package/dist/flavors/ieee/model.js +130 -20
  19. package/dist/flavors/ieee/preprocessor.js +3 -6
  20. package/dist/flavors/nist/grammar.js +18 -9
  21. package/dist/flavors/nist/index.js +16 -7
  22. package/dist/flavors/nist/preprocessor.js +9 -1
  23. package/dist/parg/runtime.d.ts +25 -0
  24. package/dist/parg/runtime.js +76 -0
  25. package/dist/parg/suite.d.ts +4 -0
  26. package/dist/parg/suite.js +14 -0
  27. package/dist/urn/parsers.js +12 -1
  28. package/package.json +6 -4
  29. package/vendor/parg/adobe.json +1 -0
  30. package/vendor/parg/aiee.json +1 -0
  31. package/vendor/parg/amca.json +1 -0
  32. package/vendor/parg/ansi.json +1 -0
  33. package/vendor/parg/api.json +1 -0
  34. package/vendor/parg/ashrae.json +1 -0
  35. package/vendor/parg/asme.json +1 -0
  36. package/vendor/parg/astm.json +1 -0
  37. package/vendor/parg/bipm.json +1 -0
  38. package/vendor/parg/bsi.json +1 -0
  39. package/vendor/parg/calconnect.json +1 -0
  40. package/vendor/parg/ccsds.json +1 -0
  41. package/vendor/parg/cen_cenelec.json +1 -0
  42. package/vendor/parg/cie.json +1 -0
  43. package/vendor/parg/csa.json +1 -0
  44. package/vendor/parg/doi.json +1 -0
  45. package/vendor/parg/easc.json +1 -0
  46. package/vendor/parg/ecma.json +1 -0
  47. package/vendor/parg/etsi.json +1 -0
  48. package/vendor/parg/evs.json +1 -0
  49. package/vendor/parg/gb.json +1 -0
  50. package/vendor/parg/gost.json +1 -0
  51. package/vendor/parg/iala.json +1 -0
  52. package/vendor/parg/iana.json +1 -0
  53. package/vendor/parg/idf.json +1 -0
  54. package/vendor/parg/iec.json +1 -0
  55. package/vendor/parg/ieee.json +1 -0
  56. package/vendor/parg/ietf.json +1 -0
  57. package/vendor/parg/ire.json +1 -0
  58. package/vendor/parg/isbn.json +1 -0
  59. package/vendor/parg/iso.json +1 -0
  60. package/vendor/parg/itu.json +1 -0
  61. package/vendor/parg/jcgm.json +1 -0
  62. package/vendor/parg/jis.json +1 -0
  63. package/vendor/parg/nesc.json +1 -0
  64. package/vendor/parg/nist.json +1 -0
  65. package/vendor/parg/oasis.json +1 -0
  66. package/vendor/parg/ogc.json +1 -0
  67. package/vendor/parg/oiml.json +1 -0
  68. package/vendor/parg/omg.json +1 -0
  69. package/vendor/parg/parg.json +1 -0
  70. package/vendor/parg/plateau.json +1 -0
  71. package/vendor/parg/sae.json +1 -0
  72. package/vendor/parg/tgpp.json +1 -0
  73. package/vendor/parg/un.json +1 -0
  74. package/vendor/parg/w3c.json +1 -0
  75. package/vendor/parg/xsf.json +1 -0
  76. package/dist/pg/runtime.d.ts +0 -85
  77. package/dist/pg/runtime.js +0 -170
  78. package/dist/pg/suite.d.ts +0 -20
  79. package/dist/pg/suite.js +0 -90
Binary file
@@ -19,13 +19,18 @@ function buildRules() {
19
19
  const anyChar = match("[\\s\\S]");
20
20
  const publisherToken = (name) => str(name);
21
21
  rule("publisher", () => publisherToken("CWA").or(publisherToken("HD"), publisherToken("ES"), publisherToken("CR"), publisherToken("ENV")).as("publisher")
22
- .or(publisherToken("EN").as("publisher").then(slash.then(publisherToken("CLC").as("copublisher")).maybe()))
23
- .or(publisherToken("CEN").as("publisher").then(slash.then(publisherToken("CLC").as("copublisher")).maybe()))
24
- .or(publisherToken("CLC").as("publisher").then(slash.then(publisherToken("CEN").as("copublisher")).maybe()))
22
+ .or(publisherToken("EN").as("publisher").then(ref(rules, "copublishers").maybe()))
23
+ .or(publisherToken("CEN").as("publisher").then(ref(rules, "copublishers").maybe()))
24
+ .or(publisherToken("CLC").as("publisher").then(ref(rules, "copublishers").maybe()))
25
25
  .or(publisherToken("EN").as("publisher")));
26
26
  rule("stage_prefix", () => str("FprEN").or(str("prEN")).as("type_with_stage"));
27
+ rule("copublishers", () => ref(rules, "copublisher").repeat(1, Infinity).as("copublishers"));
28
+ rule("copublisher", () => slash.then(publisherToken("CLC").or(publisherToken("ETSI")).as("copublisher")));
29
+ rule("version", () => space.then(str("V")).then(digits.as("v_major")).then(str(".").then(digits.as("v_minor")))
30
+ .then(str(".").then(digits.as("v_patch")))
31
+ .as("version"));
27
32
  rule("type", () => str("Guide").or(str("GUIDE"), str("TR"), str("TS")).as("type"));
28
- rule("number", () => digits.as("number"));
33
+ rule("number", () => digits.then(match(" [0-9]").then(digits).repeat(0, Infinity)).as("number"));
29
34
  rule("part", () => dash.then(match("[0-9-]").repeat(1, Infinity).as("part")));
30
35
  rule("parts", () => ref(rules, "part").repeat(0, Infinity).as("parts"));
31
36
  rule("year", () => colon.then(digit.repeat(4, 4).as("year")));
@@ -84,6 +89,7 @@ function buildRules() {
84
89
  .then(space.then(ref(rules, "adopted_string")).maybe())
85
90
  .then(space.then(ref(rules, "type")).or(slash.then(ref(rules, "type"))).maybe())
86
91
  .then(space.then(ref(rules, "number")).then(ref(rules, "parts"))
92
+ .then(ref(rules, "version").maybe())
87
93
  .then(ref(rules, "year").maybe())
88
94
  .maybe())
89
95
  .then(ref(rules, "supplements"))
@@ -1,4 +1,4 @@
1
- import { parseGrammar } from "../../grammar/engine.js";
1
+ import { parseGrammar, ParseFailed } from "../../grammar/engine.js";
2
2
  import { grammarImplementation } from "../index.js";
3
3
  import { cenCenelecGrammar, preprocessCenCenelec } from "./grammar.js";
4
4
  import { AdoptedEuropeanNormClass, AmendmentClass, CEN_DOCUMENT_CLASSES, ConsolidatedIdentifierClass, CorrigendumClass, DEFAULT_TYPED_STAGE, EuropeanPrestandardClass, FragmentClass, PUBLISHER_TYPES, locateStage, locateTypeClass, } from "./model.js";
@@ -28,6 +28,11 @@ class CenBuilder {
28
28
  if (d["fragment_number"] !== undefined) {
29
29
  return this.buildFragmentIdentifier(d);
30
30
  }
31
+ // "TR" is a CEN or CLC technical report ("CEN/TR", "CLC/TR") — the
32
+ // EN publisher never carries it (pubid ruling 2026-10-13).
33
+ if (str(d["type"]) === "TR" && str(d["publisher"]) === "EN") {
34
+ throw new ParseFailed('"EN" cannot be "TR": a technical report is "CEN/TR" or "CLC/TR"', 0);
35
+ }
31
36
  const base = this.buildBaseDocument(d);
32
37
  if (this.hasSlashSupplements(d)) {
33
38
  return this.buildStandaloneSupplement(base, d);
@@ -93,12 +98,26 @@ class CenBuilder {
93
98
  if (baseData["publisher"] !== undefined) {
94
99
  attrs["publisher"] = str(baseData["publisher"]);
95
100
  }
101
+ if (Array.isArray(baseData["copublishers"])) {
102
+ const copubs = baseData["copublishers"]
103
+ .map((c) => str(c["copublisher"]))
104
+ .filter((c) => c !== undefined);
105
+ if (copubs.length > 0)
106
+ attrs["copublishers"] = copubs;
107
+ }
96
108
  if (baseData["copublisher"] !== undefined) {
97
109
  attrs["copublishers"] = [str(baseData["copublisher"])];
98
110
  }
99
111
  if (baseData["number"] !== undefined) {
100
112
  attrs["number"] = str(baseData["number"]);
101
113
  }
114
+ if (baseData["version"] !== undefined) {
115
+ const v = baseData["version"];
116
+ // the gem's parg capture includes the leading space (" V2.2.1")
117
+ attrs["version"] = isObj(v)
118
+ ? ` V${str(v["v_major"])}.${str(v["v_minor"])}.${str(v["v_patch"])}`
119
+ : str(v);
120
+ }
102
121
  if (Array.isArray(baseData["parts"]) && baseData["parts"].length > 0) {
103
122
  const first = baseData["parts"][0];
104
123
  attrs["part"] = isObj(first) ? str(first["part"]) : str(first);
@@ -26,6 +26,7 @@ export declare abstract class CenIdentifier extends BaseIdentifier {
26
26
  static mappings: FieldMapping[];
27
27
  readonly number: string | undefined;
28
28
  readonly part: string | undefined;
29
+ readonly version: string | undefined;
29
30
  readonly date: PubidDate | undefined;
30
31
  readonly typedStage: CenTypedStage | undefined;
31
32
  readonly type: string | undefined;
@@ -102,12 +102,13 @@ export class CenIdentifier extends BaseIdentifier {
102
102
  number: { type: "string" },
103
103
  part: { type: "string" },
104
104
  date: { type: PubidDate },
105
+ version: { type: "string" },
105
106
  typedStage: { type: "string" },
106
107
  type: { type: "string" },
107
108
  publisher: { type: "string" },
108
109
  copublishers: { type: "string", collection: true },
109
110
  });
110
- static mappings = keyValue({ wire: "number", to: "number" }, { wire: "part", to: "part" }, dateWire, stageWire, { wire: "publisher", to: "publisher" }, { wire: "copublishers", to: "copublishers" });
111
+ static mappings = keyValue({ wire: "number", to: "number" }, { wire: "part", to: "part" }, dateWire, { wire: "version", to: "version" }, stageWire, { wire: "publisher", to: "publisher" }, { wire: "copublishers", to: "copublishers" });
111
112
  root() {
112
113
  return this;
113
114
  }
@@ -166,6 +167,8 @@ function renderSingle(id, classShort) {
166
167
  }
167
168
  if (id.number !== undefined) {
168
169
  parts.push(id.part !== undefined ? `${id.number}-${id.part}` : id.number);
170
+ if (id.version !== undefined)
171
+ parts[parts.length - 1] += id.version;
169
172
  }
170
173
  let result = "";
171
174
  parts.forEach((part, idx) => {
@@ -1,21 +1,67 @@
1
- import { ref, str } from "../../grammar/engine.js";
1
+ import { match, ref, str } from "../../grammar/engine.js";
2
2
  import { cenCenelecGrammar } from "../cen_cenelec/grammar.js";
3
3
  /**
4
- * Port of lib/pubid/evs/parser.rb — "EVS" ("-" | " ") <CEN identifier>,
5
- * with the CEN/CENELEC grammar's identifier rule embedded as an atom
6
- * (the adopted subtree goes straight to the CEN builder as data, no
7
- * string re-serialization).
4
+ * Port of the EVS grammar (evs.parg v0.3.0): the national prefix
5
+ * ("EVS" / "prEVS") over a CEN/CENELEC adoption, a direct ISO/IEC
6
+ * adoption, or a plain Estonian standard — plus the EVS-list
7
+ * supplements (the national annex, the COR/AC corrigendum, the AMD
8
+ * amendment spelling) with their semantic separators. The trailing
9
+ * language code extracts in the parse wrapper (the CEN adopted spine
10
+ * is greedy over plain text and would swallow it).
8
11
  */
9
12
  function buildRules() {
10
13
  const rules = {};
11
14
  const rule = (name, build) => {
12
15
  rules[name] = build();
13
16
  };
14
- rule("root", () => str("EVS")
17
+ const digit = match("[0-9]");
18
+ const digits = digit.repeat(1, Infinity);
19
+ const year4 = digit.repeat(4, 4);
20
+ const space = str(" ");
21
+ const plus = str("+");
22
+ const slash = str("/");
23
+ const colon = str(":");
24
+ // " AMD1:2014" / "+AMD1" — the AMD amendment spelling
25
+ rule("evs_amd", () => ((plus.or(slash).or(space)).as("amd_separator")
26
+ .then(str("AMD").as("amd_kind"))
27
+ .then(digits.as("amd_number"))
28
+ .then(colon.then(year4.as("amd_year")).maybe())).as("evs_amd"));
29
+ // "+COR:2015" / "/COR" — the COR spelling (the CEN spine consumes +AC)
30
+ rule("evs_cor", () => ((plus.or(slash)).as("cor_separator")
31
+ .then(str("COR").as("cor_kind"))
32
+ .then(digits.as("cor_number").maybe())
33
+ .then(colon.then(year4.as("cor_year")).maybe())).as("evs_cor"));
34
+ // " AC:2015" — the space-separated AC spelling
35
+ rule("evs_ac", () => (space.as("cor_separator")
36
+ .then(str("AC").as("cor_kind"))
37
+ .then(digits.as("cor_number").maybe())
38
+ .then(colon.then(year4.as("cor_year")).maybe())).as("evs_cor"));
39
+ // "+NA:2010" / "/NA" — the national annex
40
+ rule("evs_na", () => ((plus.or(slash)).as("na_separator")
41
+ .then(str("NA"))
42
+ .then(colon.then(year4.as("na_year")).maybe())).as("na"));
43
+ // "EVS-ISO 9001:2015", "EVS-IEC 62368-1:2020"
44
+ rule("evs_intl", () => ((str("ISO").or(str("IEC"))).as("intl_org")
45
+ .then(space)
46
+ .then(digits.as("number"))
47
+ .then((dash.then(digits.as("part"))).maybe())
48
+ .then((colon.then(year4.as("year"))).maybe())).as("intl"));
49
+ // "EVS 921:2019", "EVS-TS 1:2019", "EVS-TR 1:2019"
50
+ rule("evs_plain", () => (((str("TS").or(str("TR"))).as("evs_type").then(space)).maybe()
51
+ .then(digits.as("number"))
52
+ .then((colon.then(year4.as("year"))).maybe())).as("plain"));
53
+ rule("root", () => str("pr").as("draft_prefix").maybe()
54
+ .then(str("EVS"))
15
55
  .then(str("-").or(str(" ")).as("evs_separator"))
16
- .then(ref(cenCenelecGrammar.rules, "identifier").as("adopted")));
56
+ .then((ref(cenCenelecGrammar.rules, "identifier").as("adopted"))
57
+ .or(ref(rules, "evs_intl"))
58
+ .or(ref(rules, "evs_plain")))
59
+ .then(ref(rules, "evs_amd").maybe())
60
+ .then(ref(rules, "evs_cor").or(ref(rules, "evs_ac")).maybe())
61
+ .then(ref(rules, "evs_na").maybe()));
17
62
  return rules;
18
63
  }
64
+ const dash = str("-");
19
65
  export const evsGrammar = {
20
66
  rules: buildRules(),
21
67
  root: "root",
@@ -1,24 +1,91 @@
1
1
  import { parseGrammar } from "../../grammar/engine.js";
2
2
  import { evsGrammar } from "./grammar.js";
3
3
  import { buildCenIdentifier } from "../cen_cenelec/index.js";
4
- import { NationalAdoption } from "./model.js";
4
+ import { EstonianStandard, InternationalAdoption, NationalAdoption } from "./model.js";
5
5
  /**
6
6
  * Port of lib/pubid/evs/builder.rb — the :adopted subtree (from the
7
- * embedded CEN grammar) goes to the CEN/CENELEC builder as data.
7
+ * embedded CEN grammar) goes to the CEN/CENELEC builder as data; the
8
+ * ISO/IEC and plain shapes carry their captures directly. The trailing
9
+ * language code extracts before the grammar runs (the CEN adopted spine
10
+ * is greedy over plain text and would swallow it).
8
11
  */
9
- function buildIdentifier(tree) {
12
+ const LANGUAGE_TAIL = / (et-en|et|en)$/;
13
+ function buildIdentifier(tree, language) {
10
14
  const d = Array.isArray(tree)
11
15
  ? Object.assign({}, ...tree)
12
16
  : tree;
13
17
  const separator = d["evs_separator"] !== undefined ? String(d["evs_separator"]) : "-";
14
- const base = buildCenIdentifier(d["adopted"]);
15
- return new NationalAdoption({ base, separator });
18
+ const draft = d["draft_prefix"] !== undefined;
19
+ let id;
20
+ if (d["adopted"] !== undefined) {
21
+ // The adopted TS face carries no TS marker ("EVS-EN 16798-1:2019"):
22
+ // the TS-ness of an EN adoption is metadata, not identity — strip the
23
+ // type capture before the CEN builder routes on it.
24
+ const adoptedData = d["adopted"];
25
+ const tsAdopted = adoptedData["type"] === "TS" &&
26
+ adoptedData["publisher"] === "EN";
27
+ const cenTree = tsAdopted
28
+ ? Object.fromEntries(Object.entries(adoptedData).filter(([k]) => k !== "type"))
29
+ : adoptedData;
30
+ id = new NationalAdoption({
31
+ base: buildCenIdentifier(cenTree),
32
+ separator, draft_prefix: draft,
33
+ });
34
+ }
35
+ else if (d["intl"] !== undefined) {
36
+ const intl = d["intl"];
37
+ id = new InternationalAdoption({
38
+ number: String(intl["number"] ?? ""),
39
+ part: intl["part"] !== undefined ? String(intl["part"]) : undefined,
40
+ intl_org: String(intl["intl_org"] ?? ""),
41
+ year: intl["year"] !== undefined ? String(intl["year"]) : undefined,
42
+ separator, draft_prefix: draft,
43
+ });
44
+ }
45
+ else {
46
+ const plain = (d["plain"] ?? {});
47
+ id = new EstonianStandard({
48
+ number: String(plain["number"] ?? ""),
49
+ separator, language,
50
+ year: plain["year"] !== undefined ? String(plain["year"]) : undefined,
51
+ evs_type: plain["evs_type"] !== undefined ? String(plain["evs_type"]) : undefined,
52
+ draft_prefix: draft,
53
+ });
54
+ }
55
+ attachSupplement(id, d);
56
+ if (language !== undefined) {
57
+ id["language"] = language;
58
+ }
59
+ return id;
60
+ }
61
+ function attachSupplement(id, d) {
62
+ if (d["na"] !== undefined) {
63
+ const na = d["na"];
64
+ id["na_separator"] = na["na_separator"] !== undefined ? String(na["na_separator"]) : "+";
65
+ id["na_year"] = na["na_year"] !== undefined ? String(na["na_year"]) : undefined;
66
+ }
67
+ if (d["evs_amd"] !== undefined) {
68
+ const amd = d["evs_amd"];
69
+ id["amd_separator"] = amd["amd_separator"] !== undefined ? String(amd["amd_separator"]) : "+";
70
+ id["amd_kind"] = "AMD";
71
+ id["amd_number"] = amd["amd_number"] !== undefined ? String(amd["amd_number"]) : undefined;
72
+ id["amd_year"] = amd["amd_year"] !== undefined ? String(amd["amd_year"]) : undefined;
73
+ }
74
+ if (d["evs_cor"] !== undefined) {
75
+ const cor = d["evs_cor"];
76
+ id["cor_separator"] = cor["cor_separator"] !== undefined ? String(cor["cor_separator"]) : "+";
77
+ id["cor_kind"] = cor["cor_kind"] !== undefined ? String(cor["cor_kind"]) : "COR";
78
+ id["cor_number"] = cor["cor_number"] !== undefined ? String(cor["cor_number"]) : undefined;
79
+ id["cor_year"] = cor["cor_year"] !== undefined ? String(cor["cor_year"]) : undefined;
80
+ }
16
81
  }
17
82
  export function evsGrammarImplementation() {
18
83
  return {
19
84
  parse(input) {
20
- const tree = parseGrammar(evsGrammar, input);
21
- return buildIdentifier(tree);
85
+ const language = LANGUAGE_TAIL.exec(input)?.[1];
86
+ const body = language !== undefined ? input.slice(0, input.length - language.length - 1) : input;
87
+ const tree = parseGrammar(evsGrammar, body);
88
+ return buildIdentifier(tree, language);
22
89
  },
23
90
  };
24
91
  }
@@ -8,12 +8,48 @@ declare class EvsUrnGenerator extends BaseUrnGenerator<EvsIdentifier> {
8
8
  export declare abstract class EvsIdentifier extends BaseIdentifier {
9
9
  readonly base: CenIdentifier | undefined;
10
10
  readonly separator: string | undefined;
11
+ readonly draft_prefix: boolean | undefined;
12
+ readonly language: string | undefined;
13
+ readonly na_separator: string | undefined;
14
+ readonly na_year: string | undefined;
15
+ readonly amd_separator: string | undefined;
16
+ readonly amd_kind: string | undefined;
17
+ readonly amd_number: string | undefined;
18
+ readonly amd_year: string | undefined;
19
+ readonly cor_separator: string | undefined;
20
+ readonly cor_kind: string | undefined;
21
+ readonly cor_number: string | undefined;
22
+ readonly cor_year: string | undefined;
23
+ /** "EVS" + separator + the shape's body + the supplements + the language. */
24
+ render(): string;
25
+ protected abstract body(): string;
11
26
  }
12
27
  export declare class NationalAdoption extends EvsIdentifier {
13
28
  static polymorphicName: string;
14
29
  static urnGenerator: typeof EvsUrnGenerator;
15
30
  static attributes: import("../../model/attribute.js").AttributeTable;
16
31
  static mappings: FieldMapping[];
17
- render(): string;
32
+ protected body(): string;
33
+ }
34
+ export declare class InternationalAdoption extends EvsIdentifier {
35
+ readonly intl_org: string | undefined;
36
+ readonly number: string | undefined;
37
+ readonly part: string | undefined;
38
+ readonly year: string | undefined;
39
+ static polymorphicName: string;
40
+ static urnGenerator: typeof EvsUrnGenerator;
41
+ static attributes: import("../../model/attribute.js").AttributeTable;
42
+ static mappings: FieldMapping[];
43
+ protected body(): string;
44
+ }
45
+ export declare class EstonianStandard extends EvsIdentifier {
46
+ readonly number: string | undefined;
47
+ readonly year: string | undefined;
48
+ readonly evs_type: string | undefined;
49
+ static polymorphicName: string;
50
+ static urnGenerator: typeof EvsUrnGenerator;
51
+ static attributes: import("../../model/attribute.js").AttributeTable;
52
+ static mappings: FieldMapping[];
53
+ protected body(): string;
18
54
  }
19
55
  export {};
@@ -3,34 +3,196 @@ import { keyValue, extendAttributes } from "../../model/attribute.js";
3
3
  import { BaseUrnGenerator } from "../../model/urn-generator.js";
4
4
  import { CenIdentifier } from "../cen_cenelec/model.js";
5
5
  /**
6
- * Port of lib/pubid/evs: the national-adoption identifier wrapping a
7
- * CEN/CENELEC base, the renderer ("EVS" + separator + adopted) and the
8
- * URN generator (the adopted CEN URN with urn:cen: swapped for
9
- * urn:evs:).
6
+ * Port of lib/pubid/evs: the national identifier ("EVS" / "prEVS" over a
7
+ * CEN adoption, an ISO/IEC adoption, or a plain Estonian standard), the
8
+ * shared supplements (the national annex, the COR/AC corrigendum, the AMD
9
+ * amendment spelling, the trailing language code) and the URN faces
10
+ * (urn:evs:en:…, urn:evs:iso:…, urn:evs:921:2019, urn:evs:ts:1:2019).
10
11
  */
11
12
  const CEN_NAMESPACE = "urn:cen:";
12
13
  const EVS_NAMESPACE = "urn:evs:";
13
14
  class EvsUrnGenerator extends BaseUrnGenerator {
14
15
  generate() {
15
- const adoptedUrn = this.identifier.base?.toUrn() ?? "";
16
- if (!adoptedUrn.startsWith(CEN_NAMESPACE)) {
17
- throw new Error(`expected adopted CEN URN, got ${JSON.stringify(adoptedUrn)}`);
16
+ const id = this.identifier;
17
+ const segments = [];
18
+ if (id.draft_prefix === true)
19
+ segments.push("pr");
20
+ if (id.base) {
21
+ const adoptedUrn = id.base.toUrn();
22
+ if (!adoptedUrn.startsWith(CEN_NAMESPACE)) {
23
+ throw new Error(`expected adopted CEN URN, got ${JSON.stringify(adoptedUrn)}`);
24
+ }
25
+ segments.push(...adoptedUrn.slice(CEN_NAMESPACE.length).split(":"));
18
26
  }
19
- return EVS_NAMESPACE + adoptedUrn.slice(CEN_NAMESPACE.length);
27
+ else if (id instanceof InternationalAdoption) {
28
+ segments.push((id.intl_org ?? "").toLowerCase());
29
+ segments.push(id.part !== undefined ? `${id.number}-${id.part}` : (id.number ?? ""));
30
+ if (id.year !== undefined)
31
+ segments.push(id.year);
32
+ }
33
+ else {
34
+ const est = id; // instanceof narrowing via the else branch
35
+ if (est.evs_type !== undefined)
36
+ segments.push(est.evs_type.toLowerCase());
37
+ segments.push(est.number ?? "");
38
+ if (est.year !== undefined)
39
+ segments.push(est.year);
40
+ }
41
+ if (id.na_separator !== undefined) {
42
+ segments.push("na");
43
+ if (id.na_year !== undefined)
44
+ segments.push(id.na_year);
45
+ }
46
+ if (id.amd_separator !== undefined) {
47
+ if (id.amd_separator === "+")
48
+ segments.push("plus");
49
+ segments.push("amd");
50
+ if (id.amd_number !== undefined)
51
+ segments.push(id.amd_number);
52
+ if (id.amd_year !== undefined)
53
+ segments.push(id.amd_year);
54
+ }
55
+ if (id.cor_separator !== undefined) {
56
+ if (id.cor_separator === "+")
57
+ segments.push("plus");
58
+ segments.push("cor");
59
+ if (id.cor_number !== undefined)
60
+ segments.push(id.cor_number);
61
+ if (id.cor_year !== undefined)
62
+ segments.push(id.cor_year);
63
+ }
64
+ if (id.language !== undefined)
65
+ segments.push(id.language);
66
+ return EVS_NAMESPACE + segments.join(":");
20
67
  }
21
68
  }
22
69
  export class EvsIdentifier extends BaseIdentifier {
70
+ /** "EVS" + separator + the shape's body + the supplements + the language. */
71
+ render() {
72
+ let result = (this.draft_prefix === true ? "pr" : "") + "EVS" + (this.separator ?? "-");
73
+ result += this.body();
74
+ if (this.amd_separator !== undefined) {
75
+ result += `${this.amd_separator}AMD${this.amd_number ?? ""}`;
76
+ if (this.amd_year !== undefined)
77
+ result += `:${this.amd_year}`;
78
+ }
79
+ if (this.cor_separator !== undefined) {
80
+ result += `${this.cor_separator}${this.cor_kind ?? "COR"}${this.cor_number ?? ""}`;
81
+ if (this.cor_year !== undefined)
82
+ result += `:${this.cor_year}`;
83
+ }
84
+ if (this.na_separator !== undefined) {
85
+ result += `${this.na_separator}NA`;
86
+ if (this.na_year !== undefined)
87
+ result += `:${this.na_year}`;
88
+ }
89
+ if (this.language !== undefined)
90
+ result += ` ${this.language}`;
91
+ return result;
92
+ }
23
93
  }
94
+ const SHARED_ATTRIBUTES = {
95
+ draft_prefix: { type: "boolean", default: false },
96
+ language: { type: "string" },
97
+ na_separator: { type: "string" },
98
+ na_year: { type: "string" },
99
+ amd_separator: { type: "string" },
100
+ amd_kind: { type: "string" },
101
+ amd_number: { type: "string" },
102
+ amd_year: { type: "string" },
103
+ cor_separator: { type: "string" },
104
+ cor_kind: { type: "string" },
105
+ cor_number: { type: "string" },
106
+ cor_year: { type: "string" },
107
+ };
108
+ const SHARED_MAPPINGS = [
109
+ { wire: "draft_prefix", to: "draft_prefix" },
110
+ { wire: "language", to: "language" },
111
+ { wire: "na_separator", to: "na_separator" },
112
+ { wire: "na_year", to: "na_year" },
113
+ { wire: "amd_separator", to: "amd_separator" },
114
+ { wire: "amd_kind", to: "amd_kind" },
115
+ { wire: "amd_number", to: "amd_number" },
116
+ { wire: "amd_year", to: "amd_year" },
117
+ { wire: "cor_separator", to: "cor_separator" },
118
+ { wire: "cor_kind", to: "cor_kind" },
119
+ { wire: "cor_number", to: "cor_number" },
120
+ { wire: "cor_year", to: "cor_year" },
121
+ ];
24
122
  export class NationalAdoption extends EvsIdentifier {
25
123
  static polymorphicName = "pubid:evs:national-adoption";
26
124
  static urnGenerator = EvsUrnGenerator;
27
125
  static attributes = extendAttributes(EvsIdentifier, {
28
126
  base: { type: CenIdentifier },
29
127
  separator: { type: "string", default: "-" },
128
+ ...SHARED_ATTRIBUTES,
30
129
  });
31
- static mappings = keyValue({ wire: "base", to: "base" }, { wire: "separator", to: "separator" });
32
- render() {
33
- return `EVS${this.separator ?? "-"}${this.base?.toHuman() ?? ""}`;
130
+ static mappings = keyValue({ wire: "base", to: "base" }, { wire: "separator", to: "separator" }, ...SHARED_MAPPINGS);
131
+ body() {
132
+ return this.base?.toHuman() ?? "";
133
+ }
134
+ }
135
+ export class InternationalAdoption extends EvsIdentifier {
136
+ static polymorphicName = "pubid:evs:international-adoption";
137
+ static urnGenerator = EvsUrnGenerator;
138
+ static attributes = extendAttributes(EvsIdentifier, {
139
+ number: { type: "string" },
140
+ part: { type: "string" },
141
+ intl_org: { type: "string" },
142
+ year: { type: "string" },
143
+ language: { type: "string" },
144
+ draft_prefix: { type: "boolean", default: false },
145
+ na_separator: { type: "string" },
146
+ na_year: { type: "string" },
147
+ amd_separator: { type: "string" },
148
+ amd_kind: { type: "string" },
149
+ amd_number: { type: "string" },
150
+ amd_year: { type: "string" },
151
+ cor_separator: { type: "string" },
152
+ cor_kind: { type: "string" },
153
+ cor_number: { type: "string" },
154
+ cor_year: { type: "string" },
155
+ });
156
+ static mappings = keyValue({ wire: "number", to: "number" }, { wire: "part", to: "part" }, { wire: "intl_org", to: "intl_org" }, { wire: "year", to: "year" }, { wire: "draft_prefix", to: "draft_prefix" }, { wire: "language", to: "language" }, { wire: "na_separator", to: "na_separator" }, { wire: "na_year", to: "na_year" }, { wire: "amd_separator", to: "amd_separator" }, { wire: "amd_kind", to: "amd_kind" }, { wire: "amd_number", to: "amd_number" }, { wire: "amd_year", to: "amd_year" }, { wire: "cor_separator", to: "cor_separator" }, { wire: "cor_kind", to: "cor_kind" }, { wire: "cor_number", to: "cor_number" }, { wire: "cor_year", to: "cor_year" });
157
+ body() {
158
+ let org = `${this.intl_org} ${this.number}`;
159
+ if (this.part !== undefined)
160
+ org += `-${this.part}`;
161
+ if (this.year !== undefined)
162
+ org += `:${this.year}`;
163
+ return org;
164
+ }
165
+ }
166
+ export class EstonianStandard extends EvsIdentifier {
167
+ static polymorphicName = "pubid:evs:estonian-standard";
168
+ static urnGenerator = EvsUrnGenerator;
169
+ static attributes = extendAttributes(EvsIdentifier, {
170
+ number: { type: "string" },
171
+ separator: { type: "string", default: "-" },
172
+ language: { type: "string" },
173
+ year: { type: "string" },
174
+ evs_type: { type: "string" },
175
+ draft_prefix: { type: "boolean", default: false },
176
+ na_separator: { type: "string" },
177
+ na_year: { type: "string" },
178
+ amd_separator: { type: "string" },
179
+ amd_kind: { type: "string" },
180
+ amd_number: { type: "string" },
181
+ amd_year: { type: "string" },
182
+ cor_separator: { type: "string" },
183
+ cor_kind: { type: "string" },
184
+ cor_number: { type: "string" },
185
+ cor_year: { type: "string" },
186
+ });
187
+ static mappings = keyValue({ wire: "number", to: "number" }, { wire: "separator", to: "separator" }, { wire: "language", to: "language" }, { wire: "year", to: "year" }, { wire: "evs_type", to: "evs_type" });
188
+ body() {
189
+ let plain = this.evs_type !== undefined ? `${this.evs_type} ` : "";
190
+ plain += this.number ?? "";
191
+ if (this.year !== undefined)
192
+ plain += `:${this.year}`;
193
+ return plain;
34
194
  }
35
195
  }
36
196
  registerType(NationalAdoption);
197
+ registerType(InternationalAdoption);
198
+ registerType(EstonianStandard);
@@ -77,10 +77,11 @@ class IecBuilder {
77
77
  Object.assign(attrs, numberComponents(tree["number_with_part"]));
78
78
  // Letters-prefixed numbers (IECEE "CAB", "AD") only take digit-first
79
79
  // parts — "CAB-G01" rejects (Ruby parity; digit numbers like 61158-X
80
- // keep letter parts).
80
+ // keep letter parts). "DIR" is the exception: the Directives number
81
+ // is its own grammar branch and takes letter parts ("DIR-IEC-SUP").
81
82
  const number = attrs["number"];
82
83
  const part = attrs["part"];
83
- if (number !== undefined && /^[A-Z]{2,4}$/.test(number) &&
84
+ if (number !== undefined && /^[A-Z]{2,4}$/.test(number) && number !== "DIR" &&
84
85
  part !== undefined && /^[A-Za-z]/.test(part)) {
85
86
  throw new ParseFailed(`IEC: letter part ${part} after letters number ${number}`, 0);
86
87
  }
@@ -501,7 +502,18 @@ export function iecGrammarImplementation() {
501
502
  const parsed = urnToCode(urn);
502
503
  if (!parsed)
503
504
  throw new Error(`Invalid IEC URN: ${urn}`);
504
- const [code, lang, allParts] = parsed;
505
+ let [code, lang, allParts] = parsed;
506
+ // The "ser" slot names every part of the document: the reference
507
+ // wraps via to_all_parts, which strips the part and the date (a
508
+ // series names the document across editions, "IEC 60034 (all
509
+ // parts)"). The part rides dash-joined on the number, the date in
510
+ // its own slot.
511
+ if (allParts) {
512
+ code = code
513
+ .replace(/:[^:]*$/, "")
514
+ .replace(/\s(19|20)\d\d(-\d\d)?$/, "")
515
+ .replace(/-[^\s:-]+$/, "");
516
+ }
505
517
  // The reference parses the unprefixed rebuild and its grammar
506
518
  // defaults the IEC publisher; the ts grammar needs it explicit.
507
519
  const id = this.parse(code);
@@ -6,7 +6,7 @@ import { Language, PubidDate } from "../../model/component.js";
6
6
  * runtime attribute driving type derivation and the URN stage slot; the
7
7
  * wire hash carries only the stage CODE for non-published drafts.
8
8
  */
9
- export type IecTypeCode = "is" | "tr" | "ts" | "pas" | "srd" | "od" | "guide" | "ish" | "frag" | "trf" | "tec" | "wp" | "sttr" | "amd" | "cor";
9
+ export type IecTypeCode = "is" | "tr" | "ts" | "pas" | "srd" | "od" | "guide" | "ish" | "frag" | "trf" | "tec" | "wp" | "sttr" | "amd" | "cor" | "cs" | "ca";
10
10
  export interface TypedStage {
11
11
  code: string;
12
12
  stageCode: string;
@@ -15,7 +15,15 @@ export const TYPED_STAGES = [
15
15
  TS("trf", "published", "trf", "TRF"),
16
16
  TS("amd", "published", "amd", "AMD"),
17
17
  TS("cor", "published", "cor", "COR"),
18
- TS("np", "np", "is", "NP", "10.20"),
18
+ // IEC CS (Component Specifications) and IEC CA (Conformity Assessment):
19
+ // the abbreviation leads the code and the publisher renders as its
20
+ // default ("IEC CS 033200-TW0001", "IEC CA 01") — gem
21
+ // ComponentSpecification/ConformityAssessment.
22
+ TS("cs", "published", "cs", "CS"),
23
+ TS("ca", "published", "ca", "CA"),
24
+ // PNW owns harmonized 10.20 (gem: NP stages are 10.00/10.60/… — the
25
+ // URN "stage-10.20" slot resolves to PNW, not NP).
26
+ TS("np", "np", "is", "NP"),
19
27
  TS("pnw", "pnw", "is", "PNW", "10.20"),
20
28
  TS("pwi", "pwi", "is", "PWI", "00.00"),
21
29
  TS("anw", "anw", "is", "ANW"),
@@ -57,6 +65,8 @@ const TYPE_NAMES = {
57
65
  ts: "technical-specification",
58
66
  pas: "publicly-available-specification",
59
67
  srd: "systems-reference-document",
68
+ cs: "component-specification",
69
+ ca: "conformity-assessment",
60
70
  od: "operational-document",
61
71
  guide: "guide",
62
72
  ish: "interpretation-sheet",
@@ -414,7 +424,7 @@ function iecFragmentClass() {
414
424
  registerType(IecFragmentIdentifier);
415
425
  return IecFragmentIdentifier;
416
426
  }
417
- const SINGLE_KINDS = ["is", "tr", "ts", "pas", "srd", "od", "guide"];
427
+ const SINGLE_KINDS = ["is", "tr", "ts", "pas", "srd", "od", "guide", "cs", "ca"];
418
428
  const KIND_CLASSES = {};
419
429
  for (const kind of SINGLE_KINDS) {
420
430
  KIND_CLASSES[kind] = iecSingleClass(kind);
@@ -0,0 +1,2 @@
1
+ import type { Tree } from "../../grammar/engine.js";
2
+ export declare function artifactToBuilderTree(shape: unknown): Tree;