@pubid/pubid 0.1.27 → 0.1.29

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,21 @@ 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 whatever date trails the
1200
+ // designator. The ruling extends to the P-less joint rows
1201
+ // (pubid#597): a numeric /D designator alone is project status
1202
+ // too -- the row is a draft AND takes the P, so the marker is set
1203
+ // below when the source spelling lacked it.
1204
+ const pWithDraft = (parsed["project_marker"] !== undefined || numericDraft) &&
1205
+ (parsed["draft_version"] !== undefined || parsed["draft_iso_stage"] !== undefined ||
1206
+ parsed["draft_month"] !== undefined || parsed["draft_year"] !== undefined);
1207
+ if (pWithDraft && parsed["project_marker"] === undefined) {
1208
+ attributes["project_marker"] = true;
1209
+ parsed["project_marker"] = "P";
1210
+ }
1211
+ if (parsed["iso_published"] !== undefined && !pWithDraft &&
1182
1212
  (parsed["printed_dash_year"] !== undefined || parsed["printed_month_year"] !== undefined ||
1183
1213
  (numericDraft && parsed["draft_month"] !== undefined) ||
1184
1214
  (parsed["year"] === undefined && parsed["parameters"] !== undefined &&
@@ -1193,6 +1223,11 @@ class IeeeBuilder {
1193
1223
  if (partVal !== undefined) {
1194
1224
  printedCode += /^[._-]/.test(partVal) ? partVal : `${sep}${partVal}`;
1195
1225
  }
1226
+ // the subpart carries its separator too (C1 separator-in-capture);
1227
+ // the two-party rows split "-57-1202"-class parts into both
1228
+ const subVal = parsed["subpart"] !== undefined ? extractValue(parsed["subpart"]) : undefined;
1229
+ if (subVal !== undefined && subVal !== "")
1230
+ printedCode += subVal;
1196
1231
  // P = project: the marker is identity, preserved as spelled.
1197
1232
  if (parsed["project_marker"] !== undefined)
1198
1233
  printedCode = `P${printedCode}`;
@@ -1217,9 +1252,11 @@ class IeeeBuilder {
1217
1252
  printedAttrs["month"] = attributes["month"];
1218
1253
  }
1219
1254
  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"])}`;
1255
+ // The date trails the draft itself ("D31, Aug 2010"): the
1256
+ // normalization ledger strips the ordinal draft on the printed
1257
+ // joint face and surfaces the date as the printed date.
1258
+ printedAttrs["year"] = extractValue(parsed["draft_year"]);
1259
+ printedAttrs["month"] = extractValue(parsed["draft_month"]);
1223
1260
  }
1224
1261
  const codeObj = IeeeCode.parse(finalCode);
1225
1262
  if (codeObj !== undefined) {
@@ -1254,6 +1291,20 @@ class IeeeBuilder {
1254
1291
  // date-less form ("/D=CD1") elects the same face only when the input
1255
1292
  // spells the designator (the reference's original_input gate) — the
1256
1293
  // stage-first rows keep their ISO rendering.
1294
+ // The designation route ("IEEE P24748-4/D=FDIS") has no
1295
+ // publishers group; IEEE is the sole publisher of the D=
1296
+ // designator face, and the elections below read the builder-local
1297
+ // slots.
1298
+ if (attributes["publisher"] === undefined)
1299
+ attributes["publisher"] = "IEEE";
1300
+ if (attributes["copublisher"] === undefined)
1301
+ attributes["copublisher"] = [];
1302
+ // The model rebuilds publishers from publisher+copublisher when
1303
+ // the publishers group is absent (the reference initialize).
1304
+ if (attributes["publishers"] === undefined) {
1305
+ attributes["publishers"] = [attributes["publisher"],
1306
+ ...(attributes["copublisher"] ?? [])];
1307
+ }
1257
1308
  const dEqInput = this.originalInput.includes("/D=");
1258
1309
  const stageWordForGate = extractValue(parsed["iso_stage"]) ?? "";
1259
1310
  if (parsed["iso_stage"] !== undefined &&
@@ -1284,6 +1335,29 @@ class IeeeBuilder {
1284
1335
  if (!bareIeeeProject)
1285
1336
  attributes["lead_party"] = "ISO";
1286
1337
  let stageAbbr = extractValue(parsed["iso_stage"]);
1338
+ // The bare-IEEE project row carries the stage on the D=
1339
+ // designator with the IEEE date convention ("D=FDIS-201008",
1340
+ // pubid#203); the ISO stage-word face is not elected.
1341
+ if (bareIeeeProject && parsed["draft_stage_year"] === undefined) {
1342
+ let date = "";
1343
+ const y = attributes["year"];
1344
+ const m = String(attributes["month"] ?? "");
1345
+ if (y !== undefined) {
1346
+ date = `-${y}`;
1347
+ if (m !== "") {
1348
+ const monthNum = /^\d+$/.test(m) ? Number(m) : DRAFT_MONTH_NAMES[String(m).replace(/\.$/, "")];
1349
+ if (monthNum !== undefined)
1350
+ date += String(monthNum).padStart(2, "0");
1351
+ }
1352
+ }
1353
+ attributes["ieee_draft"] = `D=${stageAbbr}${date}`;
1354
+ const record = stageAbbr !== undefined ? locateStage(stageAbbr) : undefined;
1355
+ if (record !== undefined)
1356
+ attributes["typed_stage"] = new IeeeTypedStage(record);
1357
+ attributes["lead_party"] = "IEEE";
1358
+ delete attributes["year"];
1359
+ delete attributes["month"];
1360
+ }
1287
1361
  // The IEC "/V<version>" iteration rides glued onto the stage word
1288
1362
  // ("_CDV/V3" -> "CDV3") — the family's lossless spelling (the
1289
1363
  // reference's builder, pubid#477).
@@ -1347,8 +1421,11 @@ class IeeeBuilder {
1347
1421
  projectAttrs[k] = attributes[k];
1348
1422
  }
1349
1423
  }
1350
- if (parsed["project_marker"] !== undefined)
1351
- projectAttrs["project_marker"] = true;
1424
+ // The ruling (pubid#597): a joint row with a numeric /D designator
1425
+ // is a draft AND takes the P project marker, whatever the source
1426
+ // spelling printed -- the marker rides the identifier even when
1427
+ // the row reached it without one.
1428
+ projectAttrs["project_marker"] = true;
1352
1429
  if (pubs.length > 0) {
1353
1430
  projectAttrs["publisher"] = pubs[0];
1354
1431
  if (pubs.length > 1)
@@ -1364,8 +1441,17 @@ class IeeeBuilder {
1364
1441
  // builds the base [ISO, IEC, IEEE], lead ISO); the CD-stage rows and
1365
1442
  // the IEEE-led project rows preserve the printed order (P90003 stays
1366
1443
  // IEEE/ISO/IEC).
1444
+ // The reorder is the PUBLISHED stage-less doctrine (pubid#317)
1445
+ // only: the project rows (the P marker) and every draft-bearing
1446
+ // row keep the printed publisher order (the 2026-10-24 corpus
1447
+ // faces, pubid#595).
1448
+ const draftBearing = parsed["project_marker"] !== undefined ||
1449
+ parsed["draft_version"] !== undefined ||
1450
+ parsed["draft"] !== undefined ||
1451
+ parsed["draft_iso_stage"] !== undefined ||
1452
+ parsed["draft_stage_year"] !== undefined;
1367
1453
  if (parsed["iso_published"] !== undefined && parsed["iso_stage"] === undefined &&
1368
- Array.isArray(attributes["publishers"])) {
1454
+ !draftBearing && Array.isArray(attributes["publishers"])) {
1369
1455
  const pubs = attributes["publishers"];
1370
1456
  attributes["publishers"] = [...pubs.filter((p) => p !== "IEEE"), ...pubs.filter((p) => p === "IEEE")];
1371
1457
  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) {
@@ -487,12 +492,17 @@ export function renderIeeeBase(id) {
487
492
  }
488
493
  if (id.draft_status !== undefined)
489
494
  parts.push(id.draft_status);
490
- const shouldRenderType = /^(IEEE|AIEE)/.test(id.publisher ?? "");
495
+ const isProjectDraft = id.constructor.polymorphicName ===
496
+ "pubid:ieee:project-draft-identifier";
497
+ // A project draft carries the word Draft whatever the lead
498
+ // publisher -- the ISO-led joint drafts (pubid#597) print it too.
499
+ const shouldRenderType = /^(IEEE|AIEE)/.test(id.publisher ?? "") || isProjectDraft;
491
500
  const stageObj = id.typed_stage;
492
501
  if (shouldRenderType && !(stageObj?.projectStatus === true) &&
493
- id.type !== undefined && id.type.trim() !== "" && id.type !== "P" &&
502
+ ((id.type !== undefined && id.type.trim() !== "" && id.type !== "P") ||
503
+ isProjectDraft) &&
494
504
  !/unapproved/i.test(id.draft_status ?? "")) {
495
- let typeStr = id.type;
505
+ let typeStr = id.type ?? "Draft";
496
506
  if (typeStr.startsWith("P"))
497
507
  typeStr = typeStr.slice(1);
498
508
  // A draft carries the word Draft alone in every face (the C2
@@ -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.27",
3
+ "version": "0.1.29",
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",