@pubid/pubid 0.1.4 → 0.1.6

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.
@@ -1,5 +1,5 @@
1
1
  import { ParseFailed } from "../../grammar/engine.js";
2
- import { BaseIdentifier, registerType } from "../../model/identifier.js";
2
+ import { BaseIdentifier, registerType, resolveType } from "../../model/identifier.js";
3
3
  import { extendAttributes, keyValue } from "../../model/attribute.js";
4
4
  import { BaseUrnGenerator } from "../../model/urn-generator.js";
5
5
  const KIND_BY_TYPE = {
@@ -13,6 +13,7 @@ const KIND_BY_TYPE = {
13
13
  };
14
14
  const TYPE_STRINGS = {
15
15
  "basic-publication": "B",
16
+ "certification-system": "CS",
16
17
  document: "D",
17
18
  "expert-report": "E",
18
19
  guide: "G",
@@ -108,6 +109,29 @@ class OimlSingle extends OimlBase {
108
109
  return result;
109
110
  }
110
111
  }
112
+ /** ---- OIML-CS certification-system documents ---- */
113
+ const CS_ATTRS = {
114
+ ...SINGLE_ATTRS,
115
+ family: { type: "string" },
116
+ amendment: { type: "string" },
117
+ space_separator: { type: "boolean", default: false },
118
+ };
119
+ const CS_MAPPINGS = keyValue(...Object.keys(CS_ATTRS).map((name) => ({ wire: name, to: name })));
120
+ // "OIML-CS PD-05 Edition 6 (Amendment 1)": the number keeps its printed
121
+ // zero padding; the print states an edition instead of a year.
122
+ class OimlCertificationSystem extends OimlSingle {
123
+ static polymorphicName = "pubid:oiml:certification-system";
124
+ static attributes = extendAttributes(BaseIdentifier, CS_ATTRS);
125
+ static mappings = CS_MAPPINGS;
126
+ render() {
127
+ let result = `${this.publisher}-CS ${this.family}` +
128
+ `${this.space_separator ? " " : "-"}${this.number} Edition ${this.edition}`;
129
+ if (this.amendment)
130
+ result += ` (Amendment ${this.amendment})`;
131
+ return result;
132
+ }
133
+ }
134
+ registerType(OimlCertificationSystem);
111
135
  function oimlSingleClass(kind) {
112
136
  const isBulletin = kind === "bulletin";
113
137
  class OimlSingleIdentifier extends OimlSingle {
@@ -146,12 +170,13 @@ const SUPP_ATTRS = {
146
170
  parsed_format: { type: "string", default: "short" },
147
171
  base: { type: OimlSingle },
148
172
  supp_year: { type: "string" },
173
+ number: { type: "string" },
149
174
  trailing: { type: "boolean", default: false },
150
175
  joined: { type: "boolean", default: false },
151
176
  letter: { type: "string" },
152
177
  year_on_base: { type: "boolean", default: false },
153
178
  };
154
- const SUPP_MAPPINGS = keyValue({ wire: "language", to: "language" }, { wire: "parsed_format", to: "parsed_format" }, { wire: "base", to: "base" }, { wire: "year", to: "supp_year" }, { wire: "trailing", to: "trailing" }, { wire: "joined", to: "joined" }, { wire: "letter", to: "letter" }, { wire: "year_on_base", to: "year_on_base" });
179
+ const SUPP_MAPPINGS = keyValue({ wire: "language", to: "language" }, { wire: "parsed_format", to: "parsed_format" }, { wire: "base", to: "base" }, { wire: "year", to: "supp_year" }, { wire: "number", to: "number" }, { wire: "trailing", to: "trailing" }, { wire: "joined", to: "joined" }, { wire: "letter", to: "letter" }, { wire: "year_on_base", to: "year_on_base" });
155
180
  /** ---- supplements (Amendment / Errata) and Annex ---- */
156
181
  class OimlSupplement extends OimlBase {
157
182
  supplementType() {
@@ -178,6 +203,8 @@ class OimlSupplement extends OimlBase {
178
203
  }
179
204
  if (this.trailing) {
180
205
  let result = `${stripLanguage(this.base.render())} ${this.supplementType()}`;
206
+ if (this.number)
207
+ result += ` ${this.number}`;
181
208
  if (this.language)
182
209
  result += ` (${this.language})`;
183
210
  return result;
@@ -247,6 +274,7 @@ for (const kind of [
247
274
  ]) {
248
275
  KIND_CLASSES[kind] = oimlSingleClass(kind);
249
276
  }
277
+ KIND_CLASSES["certification-system"] = OimlCertificationSystem;
250
278
  KIND_CLASSES["amendment"] = oimlSupplementClass("amendment");
251
279
  KIND_CLASSES["errata"] = oimlSupplementClass("errata");
252
280
  KIND_CLASSES["annex"] = oimlSupplementClass("annex");
@@ -270,6 +298,13 @@ class OimlUrnGenerator extends BaseUrnGenerator {
270
298
  parts.push(id.language.toLowerCase());
271
299
  return parts.join(":");
272
300
  }
301
+ // Certification-system documents: the family-number pair is the
302
+ // document identity ("cs:pd-05"); the printed edition and trailing
303
+ // amendment are print states, not URN segments.
304
+ if (kind === "certification-system") {
305
+ const cs = id;
306
+ return ["urn", "oiml", "cs", `${(cs.family ?? "").toLowerCase()}-${cs.number ?? ""}`].join(":");
307
+ }
273
308
  const isSupp = kind === "amendment" || kind === "errata" || kind === "annex";
274
309
  const single = (isSupp ? id.base : id);
275
310
  const parts = ["urn", "oiml"];
@@ -280,8 +315,12 @@ class OimlUrnGenerator extends BaseUrnGenerator {
280
315
  const code = single.composedCode();
281
316
  if (code)
282
317
  parts.push(code);
318
+ // The trailing-word form keeps the publication year on the base
319
+ // ("R 138:2009 Amendment 1"); the supplement itself carries only the
320
+ // ordinal.
283
321
  const year = id.year ??
284
- id.supp_year;
322
+ id.supp_year ??
323
+ (isSupp ? single.year : undefined);
285
324
  if (year)
286
325
  parts.push(year);
287
326
  const stage = id.stage;
@@ -292,6 +331,12 @@ class OimlUrnGenerator extends BaseUrnGenerator {
292
331
  parts.push(iteration);
293
332
  if (id.language)
294
333
  parts.push(id.language.toLowerCase());
334
+ // Ruby's supplement branch appends the ordinal after the language.
335
+ if (isSupp) {
336
+ const ordinal = id.number;
337
+ if (ordinal)
338
+ parts.push(ordinal);
339
+ }
295
340
  return parts.join(":");
296
341
  }
297
342
  }
@@ -299,15 +344,97 @@ for (const klass of Object.values(KIND_CLASSES)) {
299
344
  klass.urnGenerator = OimlUrnGenerator;
300
345
  }
301
346
  /** ---- builder (lib/pubid/oiml/builder.rb) ---- */
347
+ // OIML co-publishes some documents jointly with another SDO (ISO so
348
+ // far); the printed reference carries both identifiers joined by "|":
349
+ // "ISO 4064-1:2024|OIML R 49-1:2024". The URN is the OIML side's.
350
+ export class OimlDualPublished extends OimlBase {
351
+ static polymorphicName = "pubid:oiml:dual-published";
352
+ static get attributes() {
353
+ return extendAttributes(OimlBase, {
354
+ // "string" keeps objects passing through the scalar coercion
355
+ // untouched (the ieee AdoptedStandard precedent for cross-flavor
356
+ // members); the constructor coerces bare hashes via the registry.
357
+ first: { type: "string" },
358
+ second: { type: "string" },
359
+ });
360
+ }
361
+ static get mappings() {
362
+ return [
363
+ { wire: "first", to: "first" },
364
+ { wire: "second", to: "second" },
365
+ ];
366
+ }
367
+ constructor(attrs = {}) {
368
+ super(attrs);
369
+ // The members are cross-flavor (the co-publisher's side arrives as a
370
+ // bare hash with _type); coerce through the registry - the abstract
371
+ // BaseIdentifier attribute type cannot dispatch polymorphically.
372
+ const coerceMember = (v) => {
373
+ if (v instanceof BaseIdentifier)
374
+ return v;
375
+ if (typeof v === "object" && v !== null && "_type" in v) {
376
+ const klass = resolveType(v["_type"]);
377
+ if (klass !== undefined) {
378
+ return klass.fromHash(v);
379
+ }
380
+ }
381
+ return undefined;
382
+ };
383
+ const self = this;
384
+ const f = coerceMember(self["first"]);
385
+ const s = coerceMember(self["second"]);
386
+ if (f !== undefined)
387
+ self["first"] = f;
388
+ if (s !== undefined)
389
+ self["second"] = s;
390
+ }
391
+ render() {
392
+ return `${this.first.toHuman()}|${this.second.toHuman()}`;
393
+ }
394
+ toUrn() {
395
+ // The URN is the OIML side's, whichever position it prints in.
396
+ const oimlSide = this.second instanceof OimlBase ? this.second : this.first;
397
+ return oimlSide.toUrn();
398
+ }
399
+ root() {
400
+ return this.second instanceof OimlBase ? this.second : this.first;
401
+ }
402
+ }
403
+ registerType(OimlDualPublished);
302
404
  export function buildOimlIdentifier(tree) {
303
405
  if (!isObj(tree))
304
406
  throw new ParseFailed("OIML: unexpected parse tree", 0);
407
+ if (tree["cs_series"] !== undefined)
408
+ return buildCs(tree);
305
409
  if (tree["amd_marker"] !== undefined)
306
410
  return buildShortAmendment(tree);
307
411
  if (tree["base"] !== undefined)
308
412
  return buildSupplement(tree);
309
413
  return buildBaseDocument(tree);
310
414
  }
415
+ // OIML-CS documents: family (PD/OD/CID), zero-padded number, the
416
+ // printed "Edition N", and an optional parenthesized trailing
417
+ // amendment. The family-number separator is a spelling flag.
418
+ function buildCs(tree) {
419
+ const attrs = {
420
+ publisher: str(tree["publisher"]) ?? "OIML",
421
+ parsed_format: "short",
422
+ };
423
+ const family = str(tree["cs_family"]);
424
+ if (family !== undefined)
425
+ attrs["family"] = family;
426
+ const number = str(tree["number"]);
427
+ if (number !== undefined)
428
+ attrs["number"] = number;
429
+ const edition = str(tree["edition"]);
430
+ if (edition !== undefined)
431
+ attrs["edition"] = edition;
432
+ const amendment = str(tree["cs_amendment"]);
433
+ if (amendment !== undefined)
434
+ attrs["amendment"] = amendment;
435
+ attrs["space_separator"] = tree["cs_separator"] === " ";
436
+ return new KIND_CLASSES["certification-system"](attrs);
437
+ }
311
438
  function buildShortAmendment(tree) {
312
439
  const baseCode = isObj(tree["base_code"]) ? tree["base_code"] : undefined;
313
440
  const base = buildBaseDocument({
@@ -359,6 +486,9 @@ function buildSupplement(tree) {
359
486
  const language = extractLanguage(tree["language"]);
360
487
  if (language !== undefined)
361
488
  attrs["language"] = language;
489
+ const ordinal = str(tree["number"]);
490
+ if (ordinal !== undefined)
491
+ attrs["number"] = ordinal;
362
492
  if (marker !== undefined)
363
493
  attrs["trailing"] = true;
364
494
  if (plusMarker !== undefined)
package/dist/index.d.ts CHANGED
@@ -6,3 +6,4 @@ export type { FlavorImplementation, Identifier, ImplementationRegistry } from ".
6
6
  export { PendingRegistry } from "./conformance/pending.js";
7
7
  export { checkCase, runFlavor, runCorpus } from "./conformance/runner.js";
8
8
  export { corpusModeImplementation, CorpusBackedIdentifier } from "./implementations/corpus-mode.js";
9
+ export { grammarImplementation } from "./flavors/index.js";
package/dist/index.js CHANGED
@@ -4,3 +4,6 @@ export { buildArtifact, loadBundledCorpus, parseArtifact, } from "./corpus/artif
4
4
  export { PendingRegistry } from "./conformance/pending.js";
5
5
  export { checkCase, runFlavor, runCorpus } from "./conformance/runner.js";
6
6
  export { corpusModeImplementation, CorpusBackedIdentifier } from "./implementations/corpus-mode.js";
7
+ // Root re-export so consumers needn't deep-import flavor modules
8
+ // (pubid/pubid-ts#63 - the estate serving codec).
9
+ export { grammarImplementation } from "./flavors/index.js";
@@ -39,6 +39,7 @@ export type UrnGeneratorCtor = new (identifier: BaseIdentifier) => {
39
39
  };
40
40
  /** Register a concrete identifier class under its polymorphic _type (the *_TYPE_MAP role). */
41
41
  export declare function registerType(klass: IdentifierStatic): void;
42
+ export declare function resolveType(type: string): IdentifierStatic | undefined;
42
43
  export declare abstract class BaseIdentifier {
43
44
  static polymorphicName: string;
44
45
  /** The human renderer body — the one method every flavor writes. */
@@ -25,7 +25,7 @@ const TYPE_REGISTRY = new Map();
25
25
  export function registerType(klass) {
26
26
  TYPE_REGISTRY.set(klass.polymorphicName, klass);
27
27
  }
28
- function resolveType(type) {
28
+ export function resolveType(type) {
29
29
  return TYPE_REGISTRY.get(type);
30
30
  }
31
31
  function isScalarType(t) {
@@ -0,0 +1,5 @@
1
+ import type { Identifier } from "../conformance/implementation.js";
2
+ import { type UrnParserDef } from "./types.js";
3
+ export declare const URN_PARSERS: Record<string, UrnParserDef>;
4
+ export declare function lookupUrnParser(flavor: string): UrnParserDef | undefined;
5
+ export declare function parseUrnFor(def: UrnParserDef, urn: string, parse: (text: string) => Identifier): Identifier;