@pubid/pubid 0.1.26 → 0.1.28

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.
@@ -37,6 +37,9 @@ function ecmaClass(kind) {
37
37
  const prefix = KIND_PREFIXES[kind];
38
38
  class EcmaKindIdentifier extends BaseIdentifier {
39
39
  static polymorphicName = `pubid:ecma:${kind}`;
40
+ // Ruby Pubid::Ecma declares subset_strict :part: a part-less ECMA
41
+ // reference means "this document has none", not "any part".
42
+ static subsetStrict = ["part"];
40
43
  static attributes = extendAttributes(BaseIdentifier, {
41
44
  number: { type: "string" },
42
45
  part: { type: "string" },
@@ -320,9 +320,14 @@ class IeeeBuilder {
320
320
  const copubs = Array.isArray(pubData["copublishers"])
321
321
  ? pubData["copublishers"]
322
322
  : [pubData["copublishers"]];
323
+ // A capture may carry its own separators ("/IEC/ISO" — a joint
324
+ // token glued to the leading slash); the collection renders
325
+ // join("/"), so entries hold bare orgs.
323
326
  attributes["copublisher"] = copubs
324
327
  .map((cp) => extractValue(asHash(cp)["copublisher"]))
325
- .filter((cp) => cp !== undefined);
328
+ .filter((cp) => cp !== undefined)
329
+ .flatMap((c) => c.replace(/^\//, "").split("/"))
330
+ .filter((c) => c !== "");
326
331
  }
327
332
  }
328
333
  else if (parsed["publisher"] !== undefined) {
@@ -637,7 +642,15 @@ class IeeeBuilder {
637
642
  }
638
643
  }
639
644
  else {
640
- version = extractValue(draftData);
645
+ // A degraded wire leaf carries the whole draft clause verbatim
646
+ // ("/D2.1.1" — the three-segment designator the engine cannot
647
+ // structure): IeeeDraft.parse is the inverse of render, so it
648
+ // strips the prefix instead of double-appending it.
649
+ const reparsed = IeeeDraft.parse(extractValue(draftData) ?? "");
650
+ if (reparsed !== undefined) {
651
+ attributes["draft"] = reparsed.render();
652
+ return;
653
+ }
641
654
  }
642
655
  // A REV-carrying draft embeds its print date into the designator
643
656
  // with the month canonicalized numeric (pubid ruling 2026-10-14):
@@ -1098,7 +1111,10 @@ class IeeeBuilder {
1098
1111
  // strip it (the #471 separator-in-capture rule). The stored
1099
1112
  // form keeps the comma so it re-parses through the same tail.
1100
1113
  const draftYear = (extractValue(parsed["draft_year"]) ?? "").replace(/^[, ]+/, "");
1101
- attributes["draft"] = `/D${draftVer}, ${draftYear}`;
1114
+ // Compose through the render so a designator-word version
1115
+ // ("ED1") keeps its word instead of concatenating D ("DED1").
1116
+ attributes["draft"] =
1117
+ new IeeeDraft({ version: draftVer, year: draftYear }).render();
1102
1118
  if (parsed["year"] === undefined)
1103
1119
  delete attributes["year"];
1104
1120
  }
@@ -1178,7 +1194,14 @@ class IeeeBuilder {
1178
1194
  // the P and keep the joint spelling.
1179
1195
  const numericDraft = parsed["draft_version"] !== undefined &&
1180
1196
  /^\d/.test(extractValue(parsed["draft_version"]) ?? "");
1181
- if (parsed["iso_published"] !== undefined &&
1197
+ // P + a /D designator (any draft capture) is a DRAFT, never a
1198
+ // printed Std (the owner's 2026-10-24 ruling) -- the joint/
1199
+ // project-draft family owns the row; the non-P rows keep the
1200
+ // printed-strip doctrine (the normalization ledger).
1201
+ const pWithDraft = parsed["project_marker"] !== undefined &&
1202
+ (parsed["draft_version"] !== undefined || parsed["draft_iso_stage"] !== undefined ||
1203
+ parsed["draft_month"] !== undefined || parsed["draft_year"] !== undefined);
1204
+ if (parsed["iso_published"] !== undefined && !pWithDraft &&
1182
1205
  (parsed["printed_dash_year"] !== undefined || parsed["printed_month_year"] !== undefined ||
1183
1206
  (numericDraft && parsed["draft_month"] !== undefined) ||
1184
1207
  (parsed["year"] === undefined && parsed["parameters"] !== undefined &&
@@ -1193,6 +1216,11 @@ class IeeeBuilder {
1193
1216
  if (partVal !== undefined) {
1194
1217
  printedCode += /^[._-]/.test(partVal) ? partVal : `${sep}${partVal}`;
1195
1218
  }
1219
+ // the subpart carries its separator too (C1 separator-in-capture);
1220
+ // the two-party rows split "-57-1202"-class parts into both
1221
+ const subVal = parsed["subpart"] !== undefined ? extractValue(parsed["subpart"]) : undefined;
1222
+ if (subVal !== undefined && subVal !== "")
1223
+ printedCode += subVal;
1196
1224
  // P = project: the marker is identity, preserved as spelled.
1197
1225
  if (parsed["project_marker"] !== undefined)
1198
1226
  printedCode = `P${printedCode}`;
@@ -1217,9 +1245,11 @@ class IeeeBuilder {
1217
1245
  printedAttrs["month"] = attributes["month"];
1218
1246
  }
1219
1247
  else if (parsed["draft_month"] !== undefined) {
1220
- // The date trails the draft itself; keep it inside the draft so
1221
- // the code stays date-less.
1222
- printedDraft = `${printedDraft}, ${extractValue(parsed["draft_month"])} ${extractValue(parsed["draft_year"])}`;
1248
+ // The date trails the draft itself ("D31, Aug 2010"): the
1249
+ // normalization ledger strips the ordinal draft on the printed
1250
+ // joint face and surfaces the date as the printed date.
1251
+ printedAttrs["year"] = extractValue(parsed["draft_year"]);
1252
+ printedAttrs["month"] = extractValue(parsed["draft_month"]);
1223
1253
  }
1224
1254
  const codeObj = IeeeCode.parse(finalCode);
1225
1255
  if (codeObj !== undefined) {
@@ -1254,6 +1284,20 @@ class IeeeBuilder {
1254
1284
  // date-less form ("/D=CD1") elects the same face only when the input
1255
1285
  // spells the designator (the reference's original_input gate) — the
1256
1286
  // stage-first rows keep their ISO rendering.
1287
+ // The designation route ("IEEE P24748-4/D=FDIS") has no
1288
+ // publishers group; IEEE is the sole publisher of the D=
1289
+ // designator face, and the elections below read the builder-local
1290
+ // slots.
1291
+ if (attributes["publisher"] === undefined)
1292
+ attributes["publisher"] = "IEEE";
1293
+ if (attributes["copublisher"] === undefined)
1294
+ attributes["copublisher"] = [];
1295
+ // The model rebuilds publishers from publisher+copublisher when
1296
+ // the publishers group is absent (the reference initialize).
1297
+ if (attributes["publishers"] === undefined) {
1298
+ attributes["publishers"] = [attributes["publisher"],
1299
+ ...(attributes["copublisher"] ?? [])];
1300
+ }
1257
1301
  const dEqInput = this.originalInput.includes("/D=");
1258
1302
  const stageWordForGate = extractValue(parsed["iso_stage"]) ?? "";
1259
1303
  if (parsed["iso_stage"] !== undefined &&
@@ -1284,6 +1328,29 @@ class IeeeBuilder {
1284
1328
  if (!bareIeeeProject)
1285
1329
  attributes["lead_party"] = "ISO";
1286
1330
  let stageAbbr = extractValue(parsed["iso_stage"]);
1331
+ // The bare-IEEE project row carries the stage on the D=
1332
+ // designator with the IEEE date convention ("D=FDIS-201008",
1333
+ // pubid#203); the ISO stage-word face is not elected.
1334
+ if (bareIeeeProject && parsed["draft_stage_year"] === undefined) {
1335
+ let date = "";
1336
+ const y = attributes["year"];
1337
+ const m = String(attributes["month"] ?? "");
1338
+ if (y !== undefined) {
1339
+ date = `-${y}`;
1340
+ if (m !== "") {
1341
+ const monthNum = /^\d+$/.test(m) ? Number(m) : DRAFT_MONTH_NAMES[String(m).replace(/\.$/, "")];
1342
+ if (monthNum !== undefined)
1343
+ date += String(monthNum).padStart(2, "0");
1344
+ }
1345
+ }
1346
+ attributes["ieee_draft"] = `D=${stageAbbr}${date}`;
1347
+ const record = stageAbbr !== undefined ? locateStage(stageAbbr) : undefined;
1348
+ if (record !== undefined)
1349
+ attributes["typed_stage"] = new IeeeTypedStage(record);
1350
+ attributes["lead_party"] = "IEEE";
1351
+ delete attributes["year"];
1352
+ delete attributes["month"];
1353
+ }
1287
1354
  // The IEC "/V<version>" iteration rides glued onto the stage word
1288
1355
  // ("_CDV/V3" -> "CDV3") — the family's lossless spelling (the
1289
1356
  // reference's builder, pubid#477).
@@ -1364,8 +1431,17 @@ class IeeeBuilder {
1364
1431
  // builds the base [ISO, IEC, IEEE], lead ISO); the CD-stage rows and
1365
1432
  // the IEEE-led project rows preserve the printed order (P90003 stays
1366
1433
  // IEEE/ISO/IEC).
1434
+ // The reorder is the PUBLISHED stage-less doctrine (pubid#317)
1435
+ // only: the project rows (the P marker) and every draft-bearing
1436
+ // row keep the printed publisher order (the 2026-10-24 corpus
1437
+ // faces, pubid#595).
1438
+ const draftBearing = parsed["project_marker"] !== undefined ||
1439
+ parsed["draft_version"] !== undefined ||
1440
+ parsed["draft"] !== undefined ||
1441
+ parsed["draft_iso_stage"] !== undefined ||
1442
+ parsed["draft_stage_year"] !== undefined;
1367
1443
  if (parsed["iso_published"] !== undefined && parsed["iso_stage"] === undefined &&
1368
- Array.isArray(attributes["publishers"])) {
1444
+ !draftBearing && Array.isArray(attributes["publishers"])) {
1369
1445
  const pubs = attributes["publishers"];
1370
1446
  attributes["publishers"] = [...pubs.filter((p) => p !== "IEEE"), ...pubs.filter((p) => p === "IEEE")];
1371
1447
  attributes["publisher"] = attributes["publishers"][0];
@@ -222,7 +222,12 @@ export class IeeeDraft {
222
222
  });
223
223
  }
224
224
  render() {
225
- let result = `/D${this.version ?? ""}`;
225
+ // A version that already opens with a designator word ("ED4", the
226
+ // IEC edition-draft ordinal; an ISO/IEC stage token such as
227
+ // "FDIS_March") renders verbatim — prepending D concatenates a
228
+ // hallucinated token ("DED4", "DFDIS") that no face spells.
229
+ const designatorVerbatim = /^(ED\d|(FDIS|FCD|CDV|DIS|CD|WD|PWI|NP))/.test(this.version);
230
+ let result = designatorVerbatim ? `/${this.version}` : `/D${this.version}`;
226
231
  if (this.isoStage !== undefined)
227
232
  result += `=${this.isoStage}`;
228
233
  if (this.isoStage !== undefined && this.isoIteration !== undefined) {
@@ -40,6 +40,10 @@ export declare class IsoEdition extends Component {
40
40
  toWire(): Record<string, unknown>;
41
41
  }
42
42
  export declare abstract class IsoIdentifier extends BaseIdentifier {
43
+ /** The typed-stage state joins the match (the published default is
44
+ * stated: ISO 9004 does not match ISO/DIS 9004) but never the wire
45
+ * hash — the attribute table drives toHash, so it is named here. */
46
+ static subsetAttributes: string[];
43
47
  static attributes: import("../../model/attribute.js").AttributeTable;
44
48
  readonly number: string | undefined;
45
49
  readonly part: string | undefined;
@@ -75,7 +79,16 @@ export declare abstract class IsoIdentifier extends BaseIdentifier {
75
79
  /** identifier.rb's base key_value block (the single-document whitelist). */
76
80
  export declare const ISO_MAPPINGS: FieldMapping[];
77
81
  /** The ISO Publisher component: "ISO/IEC/IEEE". */
78
- export declare class IsoPublisher {
82
+ export declare class IsoPublisher extends Component {
83
+ static attributes: {
84
+ publisher: {
85
+ type: string;
86
+ };
87
+ copublisher: {
88
+ type: string;
89
+ collection: boolean;
90
+ };
91
+ };
79
92
  readonly publisher: string;
80
93
  readonly copublisher: string[] | undefined;
81
94
  constructor(attrs: {
@@ -83,6 +96,7 @@ export declare class IsoPublisher {
83
96
  copublisher?: unknown;
84
97
  });
85
98
  render(): string;
99
+ toWire(): Record<string, unknown>;
86
100
  }
87
101
  /** Single documents (everything except supplements, TcDocument, Bundled). */
88
102
  export declare abstract class IsoSingleIdentifier extends IsoIdentifier {
@@ -60,6 +60,10 @@ export class IsoEdition extends Component {
60
60
  }
61
61
  }
62
62
  export class IsoIdentifier extends BaseIdentifier {
63
+ /** The typed-stage state joins the match (the published default is
64
+ * stated: ISO 9004 does not match ISO/DIS 9004) but never the wire
65
+ * hash — the attribute table drives toHash, so it is named here. */
66
+ static subsetAttributes = ["typedStageState"];
63
67
  static attributes = extendAttributes(BaseIdentifier, {
64
68
  number: { type: "string" },
65
69
  part: { type: "string" },
@@ -275,10 +279,15 @@ const stageMapping = () => ({
275
279
  },
276
280
  });
277
281
  /** The ISO Publisher component: "ISO/IEC/IEEE". */
278
- export class IsoPublisher {
282
+ export class IsoPublisher extends Component {
283
+ static attributes = {
284
+ publisher: { type: "string" },
285
+ copublisher: { type: "string", collection: true },
286
+ };
279
287
  publisher;
280
288
  copublisher;
281
289
  constructor(attrs) {
290
+ super();
282
291
  this.publisher = attrs.publisher === undefined || attrs.publisher === null ? "ISO" : String(attrs.publisher);
283
292
  this.copublisher = Array.isArray(attrs.copublisher) ? attrs.copublisher.map(String) : [];
284
293
  }
@@ -287,6 +296,12 @@ export class IsoPublisher {
287
296
  return this.publisher;
288
297
  return this.publisher + this.copublisher.map((cp) => `/${cp}`).join("");
289
298
  }
299
+ toWire() {
300
+ return {
301
+ publisher: this.publisher,
302
+ ...(this.copublisher && this.copublisher.length > 0 ? { copublisher: this.copublisher } : {}),
303
+ };
304
+ }
290
305
  }
291
306
  /** Single documents (everything except supplements, TcDocument, Bundled). */
292
307
  export class IsoSingleIdentifier extends IsoIdentifier {
package/dist/index.d.ts CHANGED
@@ -7,3 +7,4 @@ 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
9
  export { grammarImplementation } from "./flavors/index.js";
10
+ export { subsetMatch, valueMatch, exactMatch } from "./model/subset-match.js";
package/dist/index.js CHANGED
@@ -7,3 +7,4 @@ export { corpusModeImplementation, CorpusBackedIdentifier } from "./implementati
7
7
  // Root re-export so consumers needn't deep-import flavor modules
8
8
  // (pubid/pubid-ts#63 - the estate serving codec).
9
9
  export { grammarImplementation } from "./flavors/index.js";
10
+ export { subsetMatch, valueMatch, exactMatch } from "./model/subset-match.js";
@@ -121,7 +121,8 @@ export function subsetMatch(reference, candidate) {
121
121
  ? ALL_PARTS_ATTRIBUTES
122
122
  : []),
123
123
  ]);
124
- return Object.keys(ctor.attributes).every((name) => {
124
+ const names = [...Object.keys(ctor.attributes), ...(ctor.subsetAttributes ?? [])];
125
+ return names.every((name) => {
125
126
  if (ignored.has(name))
126
127
  return true;
127
128
  const mine = reference[name];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubid/pubid",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
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",