@pubid/pubid 0.1.9 → 0.1.11

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 (52) hide show
  1. package/artifact/corpus.json.gz +0 -0
  2. package/dist/flavors/ieee/index.js +136 -19
  3. package/dist/flavors/ieee/model.js +90 -34
  4. package/dist/flavors/ieee/preprocessor.js +46 -1
  5. package/package.json +2 -2
  6. package/vendor/parg/adobe.json +1 -1
  7. package/vendor/parg/aiee.json +1 -1
  8. package/vendor/parg/amca.json +1 -1
  9. package/vendor/parg/ansi.json +1 -1
  10. package/vendor/parg/api.json +1 -1
  11. package/vendor/parg/ashrae.json +1 -1
  12. package/vendor/parg/asme.json +1 -1
  13. package/vendor/parg/astm.json +1 -1
  14. package/vendor/parg/bipm.json +1 -1
  15. package/vendor/parg/bsi.json +1 -1
  16. package/vendor/parg/calconnect.json +1 -1
  17. package/vendor/parg/ccsds.json +1 -1
  18. package/vendor/parg/cen_cenelec.json +1 -1
  19. package/vendor/parg/cie.json +1 -1
  20. package/vendor/parg/csa.json +1 -1
  21. package/vendor/parg/doi.json +1 -1
  22. package/vendor/parg/easc.json +1 -1
  23. package/vendor/parg/ecma.json +1 -1
  24. package/vendor/parg/etsi.json +1 -1
  25. package/vendor/parg/evs.json +1 -1
  26. package/vendor/parg/gb.json +1 -1
  27. package/vendor/parg/gost.json +1 -1
  28. package/vendor/parg/iala.json +1 -1
  29. package/vendor/parg/iana.json +1 -1
  30. package/vendor/parg/idf.json +1 -1
  31. package/vendor/parg/iec.json +1 -1
  32. package/vendor/parg/ieee.json +1 -1
  33. package/vendor/parg/ietf.json +1 -1
  34. package/vendor/parg/ire.json +1 -1
  35. package/vendor/parg/isbn.json +1 -1
  36. package/vendor/parg/iso.json +1 -1
  37. package/vendor/parg/itu.json +1 -1
  38. package/vendor/parg/jcgm.json +1 -1
  39. package/vendor/parg/jis.json +1 -1
  40. package/vendor/parg/nesc.json +1 -1
  41. package/vendor/parg/nist.json +1 -1
  42. package/vendor/parg/oasis.json +1 -1
  43. package/vendor/parg/ogc.json +1 -1
  44. package/vendor/parg/oiml.json +1 -1
  45. package/vendor/parg/omg.json +1 -1
  46. package/vendor/parg/parg.json +1 -1
  47. package/vendor/parg/plateau.json +1 -1
  48. package/vendor/parg/sae.json +1 -1
  49. package/vendor/parg/tgpp.json +1 -1
  50. package/vendor/parg/un.json +1 -1
  51. package/vendor/parg/w3c.json +1 -1
  52. package/vendor/parg/xsf.json +1 -1
Binary file
@@ -119,7 +119,7 @@ class IeeeBuilder {
119
119
  }
120
120
  const dash = drafts.find((e) => isObj(e) && e["dash_year"] !== undefined) ??
121
121
  (d["dash_year"] !== undefined ? d : undefined);
122
- if (dash === undefined || d["year"] !== undefined)
122
+ if (dash === undefined)
123
123
  return;
124
124
  const objs = drafts.filter(isObj);
125
125
  const joined = objs
@@ -151,7 +151,7 @@ class IeeeBuilder {
151
151
  delete dash["dash_year"];
152
152
  delete dash["dash_month"];
153
153
  }
154
- else {
154
+ else if (d["year"] === undefined) {
155
155
  d["year"] = dashYear;
156
156
  d["month"] = dm;
157
157
  }
@@ -163,7 +163,7 @@ class IeeeBuilder {
163
163
  delete dash["dash_year"];
164
164
  delete dash["dash_month"];
165
165
  }
166
- else {
166
+ else if (d["year"] === undefined) {
167
167
  d["year"] = dashYear;
168
168
  d["month"] = extractValue(dashMonth);
169
169
  }
@@ -339,6 +339,10 @@ class IeeeBuilder {
339
339
  }
340
340
  if (typeValue === undefined && this.originalInput !== "" && /ANSI\s+P/.test(this.originalInput)) {
341
341
  typeValue = "P";
342
+ // The P is identity on a non-IEEE-led publisher too (the C1
343
+ // ruling): record the marker so the render prepends it —
344
+ // "ANSI PN42.34/D9a, 2015" round-trips.
345
+ attributes["project_marker"] = true;
342
346
  }
343
347
  if (codeStr !== undefined && codeStr.startsWith("P") && this.originalInput.includes("/CSA")) {
344
348
  const pubData = parsed["publishers"] !== undefined ? asHash(parsed["publishers"]) : undefined;
@@ -480,7 +484,10 @@ class IeeeBuilder {
480
484
  const dv = draftData["draft_version"];
481
485
  const version = Array.isArray(dv) ? dv.map((v) => extractValue(v) ?? "").join("") : extractValue(dv);
482
486
  if (version !== undefined) {
483
- const draftAbbr = `D${version}`;
487
+ // relaton's hyphenated "/D-2" spelling captures the dash inside
488
+ // draft_version; the stage registry keys on "D2". A date-embedded
489
+ // version ("3-2017-07") keys on its leading ordinal.
490
+ const draftAbbr = `D${version.replace(/^-/, "").replace(/-\d{4}(-\d{2})?$/, "")}`;
484
491
  const stage = locateStage(draftAbbr);
485
492
  if (stage !== undefined && stage.abbr.includes(draftAbbr))
486
493
  return draftAbbr;
@@ -532,6 +539,15 @@ class IeeeBuilder {
532
539
  revision = extractValue(draftData["revision"]);
533
540
  isoStage = extractValue(draftData["draft_iso_stage"]);
534
541
  isoIteration = extractValue(draftData["draft_iso_iteration"]);
542
+ // FDIS/CDV/… are ISO/IEC stage tokens, not draft ordinals (C1
543
+ // ruling, testsuite#2). "/DDIS" and "/DFDIS" are hallucinated
544
+ // concatenations — the designator D does not merge onto a stage
545
+ // word. The draft of a stage rides the iso_stage slot, which
546
+ // Draft#to_s renders as the D= designator face ("…/D=DIS-2020").
547
+ if (version !== undefined && /^(FDIS|FCD|CDV|DIS|CD|WD|PWI|NP)$/.test(version)) {
548
+ isoStage = version;
549
+ version = undefined;
550
+ }
535
551
  month = extractValue(draftData["month"]);
536
552
  year = extractValue(draftData["year"]);
537
553
  // The apostrophe-year catalogue convention ("P1484.20.1/D7,
@@ -567,7 +583,8 @@ class IeeeBuilder {
567
583
  month = year = day = undefined;
568
584
  }
569
585
  }
570
- if (version !== undefined || (isObj(draftData) && draftData["draft_iso_stage"] !== undefined)) {
586
+ if (version !== undefined || isoStage !== undefined ||
587
+ (isObj(draftData) && draftData["draft_iso_stage"] !== undefined)) {
571
588
  const draftObj = new IeeeDraft({
572
589
  version,
573
590
  revision,
@@ -821,6 +838,15 @@ class IeeeBuilder {
821
838
  const codeParts = [];
822
839
  if (parsed["part"] !== undefined)
823
840
  codeParts.push(extractValue(parsed["part"]) ?? "");
841
+ // The part/subpart captures carry their printed separator
842
+ // (separator-in-capture); the subpart rides after the part
843
+ // ("P65700.19.03", "60079-30-2" — the reference's joint code
844
+ // assembly).
845
+ if (parsed["subpart"] !== undefined) {
846
+ const subs = Array.isArray(parsed["subpart"]) ? parsed["subpart"] : [parsed["subpart"]];
847
+ for (const sp of subs)
848
+ codeParts.push(extractValue(sp) ?? "");
849
+ }
824
850
  let codeStr = extractValue(parsed["number"]);
825
851
  if (codeStr !== undefined && codeParts.length > 0) {
826
852
  // The ISO/IEC label's part may be dash-joined ("8802-9") - keep the
@@ -859,6 +885,39 @@ class IeeeBuilder {
859
885
  if (attributes["month"] === undefined)
860
886
  attributes["month"] = extractValue(parsed["draft_month"]);
861
887
  }
888
+ // The IEEE-led tail word-stage ("IEEE/ISO/IEC P15026-3/CD,
889
+ // December 2021") composes the D= designator with the glued
890
+ // 6-digit date — the identity its D= twin spelling parses to (the
891
+ // corpus canonical: joint-development, lead IEEE, stage P). The
892
+ // ISO-led lists converge to the published-Std identity (the issue
893
+ // #216 semantics). The zero-width printed_month_year capture
894
+ // carries "" — truthy in the reference (nil-check), falsy in JS:
895
+ // test presence, not truth.
896
+ if (parsed["tail_iso_stage"] !== undefined &&
897
+ parsed["printed_month_year"] !== undefined &&
898
+ parsed["part"] !== undefined) {
899
+ const stage = extractValue(parsed["tail_iso_stage"]);
900
+ const year = extractValue(parsed["year"]) ?? "";
901
+ const monthName = (extractValue(parsed["month"]) ?? "").replace(/\.$/, "");
902
+ const monthNum = DRAFT_MONTH_NAMES[monthName];
903
+ if (stage !== undefined && monthNum !== undefined) {
904
+ attributes["ieee_draft"] = `D=${stage}-${year}${monthNum.padStart(2, "0")}`;
905
+ parsed["year"] = undefined;
906
+ parsed["month"] = undefined;
907
+ parsed["printed_month_year"] = undefined;
908
+ delete attributes["year"];
909
+ delete attributes["month"];
910
+ // The ISO-led lists converge to the published-Std identity; the
911
+ // IEEE-led lists stay the live project draft (stage P).
912
+ if (attributes["publisher"] !== "IEEE") {
913
+ parsed["iso_published"] = { value: "" };
914
+ const stdRecord = locateStage("Std");
915
+ if (stdRecord !== undefined) {
916
+ attributes["typed_stage"] = new IeeeTypedStage(stdRecord);
917
+ }
918
+ }
919
+ }
920
+ }
862
921
  if (parsed["edition"] !== undefined)
863
922
  attributes["edition"] = extractValue(parsed["edition"]);
864
923
  if (parsed["edition_month"] !== undefined) {
@@ -874,13 +933,28 @@ class IeeeBuilder {
874
933
  // the ISO face, which prints the date from the identity slots
875
934
  // (", March 2017", pubid#216).
876
935
  if (parsed["draft_month"] !== undefined && parsed["iso_stage"] === undefined) {
877
- const draftObj = new IeeeDraft({
878
- version: draftVer,
879
- month: extractValue(parsed["draft_month"]),
880
- year: extractValue(parsed["draft_year"]),
881
- comma_before_month: true,
882
- });
883
- attributes["draft"] = draftObj.render();
936
+ const dm = extractValue(parsed["draft_month"]) ?? "";
937
+ const dy = extractValue(parsed["draft_year"]) ?? "";
938
+ const monthNum = DRAFT_MONTH_NAMES[dm.replace(/\.$/, "")];
939
+ if (JSON.stringify(attributes["publishers"]) === JSON.stringify(["IEEE", "CSA"]) &&
940
+ monthNum !== undefined && dy !== "") {
941
+ // The joint IEEE/CSA drafts glue their date into the
942
+ // designator with the month numeric (pubid ruling
943
+ // 2026-10-14: no comma dates on these rows).
944
+ const glued = new IeeeDraft({
945
+ version: `${draftVer}-${dy}-${monthNum.padStart(2, "0")}`,
946
+ });
947
+ attributes["draft"] = glued.render();
948
+ }
949
+ else {
950
+ const draftObj = new IeeeDraft({
951
+ version: draftVer,
952
+ month: dm === "" ? undefined : dm,
953
+ year: dy === "" ? undefined : dy,
954
+ comma_before_month: true,
955
+ });
956
+ attributes["draft"] = draftObj.render();
957
+ }
884
958
  if (parsed["year"] === undefined)
885
959
  delete attributes["year"];
886
960
  if (parsed["month"] === undefined)
@@ -895,6 +969,21 @@ class IeeeBuilder {
895
969
  if (parsed["year"] === undefined)
896
970
  delete attributes["year"];
897
971
  }
972
+ else if (JSON.stringify(attributes["publishers"]) === JSON.stringify(["IEEE", "CSA"]) &&
973
+ parsed["iso_stage"] === undefined &&
974
+ /^(19|20)\d\d$/.test(extractValue(parsed["year"]) ?? "") &&
975
+ /^\d{1,2}$/.test(extractValue(parsed["month"]) ?? "")) {
976
+ // The glued face re-parses with the date in the identity
977
+ // slots ("D2-2016-09" -> year/month + a bare D2): it
978
+ // normalizes back onto the draft-embedded date so the
979
+ // round trip closes on the CSA canonical.
980
+ const glued = new IeeeDraft({
981
+ version: `${draftVer}-${extractValue(parsed["year"])}-${extractValue(parsed["month"])}`,
982
+ });
983
+ attributes["draft"] = glued.render();
984
+ delete attributes["year"];
985
+ delete attributes["month"];
986
+ }
898
987
  else {
899
988
  attributes["ieee_draft"] = `D${draftVer}`;
900
989
  }
@@ -941,9 +1030,10 @@ class IeeeBuilder {
941
1030
  jointDraft += `.${extractValue(parsed["draft_iso_iteration"])}`;
942
1031
  }
943
1032
  if (parsed["draft_stage_year"] !== undefined) {
944
- // The capture carries its separator (":2020") — strip it (the
945
- // #471 separator-in-capture rule).
946
- jointDraft += `:${(extractValue(parsed["draft_stage_year"]) ?? "").replace(/^[\-:]/, "")}`;
1033
+ // The capture carries its separator verbatim (":2020" /
1034
+ // "-201112" - the #471 separator-in-capture rule): the ruby
1035
+ // builder appends it raw, so the face keeps the printed form.
1036
+ jointDraft += extractValue(parsed["draft_stage_year"]) ?? "";
947
1037
  }
948
1038
  attributes["ieee_draft"] = jointDraft;
949
1039
  }
@@ -1035,9 +1125,22 @@ class IeeeBuilder {
1035
1125
  else {
1036
1126
  attributes["lead_party"] = "IEEE";
1037
1127
  attributes["type"] = "P";
1038
- const record = locateStage("P");
1039
- if (record !== undefined)
1040
- attributes["typed_stage"] = new IeeeTypedStage(record);
1128
+ if ((parsed["draft_iso_stage"] !== undefined ||
1129
+ parsed["tail_iso_stage"] !== undefined) &&
1130
+ attributes["publisher"] === "ISO") {
1131
+ // The ISO-led D= designator (the issue #216 convergence) is
1132
+ // the stage-tracked draft of a published standard - the typed
1133
+ // stage reads Std/published.
1134
+ const stdRecord = locateStage("Std");
1135
+ if (stdRecord !== undefined) {
1136
+ attributes["typed_stage"] = new IeeeTypedStage(stdRecord);
1137
+ }
1138
+ }
1139
+ else {
1140
+ const record = locateStage("P");
1141
+ if (record !== undefined)
1142
+ attributes["typed_stage"] = new IeeeTypedStage(record);
1143
+ }
1041
1144
  }
1042
1145
  // A plain IEEE-designator draft on joint publishers ("/D8, June
1043
1146
  // 2010") carries no ISO stage machinery: it builds as the project
@@ -1049,9 +1152,23 @@ class IeeeBuilder {
1049
1152
  parsed["amd_number"] === undefined) {
1050
1153
  const pubs = (attributes["publishers"] ?? []);
1051
1154
  const projectAttrs = {
1052
- code: attributes["code"],
1053
1155
  draft: attributes["draft"],
1054
1156
  };
1157
+ // The comma-year tail rides the draft verbatim ("D4, 2019" — the
1158
+ // reference keeps the comma face on the joint project drafts; the
1159
+ // year never promotes to the identity slots).
1160
+ // The code columns (number/parts/separator/prefix) are the
1161
+ // ProjectDraft render/hash face; the spelled P is the project
1162
+ // marker (identity), whatever the lead publisher.
1163
+ for (const k of ["number", "prefix", "parts", "separator"]) {
1164
+ // A "P" prefix is the project marker itself (the generic path
1165
+ // deletes it on the strip) - it must not ride the code columns.
1166
+ if (attributes[k] !== undefined && !(k === "prefix" && attributes[k] === "P")) {
1167
+ projectAttrs[k] = attributes[k];
1168
+ }
1169
+ }
1170
+ if (parsed["project_marker"] !== undefined)
1171
+ projectAttrs["project_marker"] = true;
1055
1172
  if (pubs.length > 0) {
1056
1173
  projectAttrs["publisher"] = pubs[0];
1057
1174
  if (pubs.length > 1)
@@ -186,8 +186,11 @@ export class IeeeDraft {
186
186
  // The compound stage suffix (docs/IEEE-DRAFT-STAGES.md §1.3):
187
187
  // "=DIS.3" is canonical - D (draft) = DIS (the stage), iteration 3.
188
188
  // "=DDIS.3", "=DDIS3", "=DDIS-3" (the stage echoed with its own D)
189
- // are accepted aliases. Captures [stage, iteration].
190
- const compound = /=D?(PWI|NP|WD|CDV|FDIS|DIS|CD)(?:[.-]?(\d+))?$/;
189
+ // are accepted aliases. Captures [stage, iteration]. The iteration
190
+ // ordinal stays short (one or two digits): a glued D= date
191
+ // ("-201908", the pubid#203 convention) must ride the version body
192
+ // verbatim, not fold into the iteration slot.
193
+ const compound = /=D?(PWI|NP|WD|CDV|FDIS|DIS|CD)(?:[.-]?(\d{1,2}[a-z]?))?$/;
191
194
  let isoStage;
192
195
  let isoIteration;
193
196
  const cm = compound.exec(body);
@@ -200,7 +203,7 @@ export class IeeeDraft {
200
203
  .sort((a, b) => b.length - a.length)
201
204
  .map((m) => `${m.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.?`)
202
205
  .join("|");
203
- const suffix = new RegExp(`(?:(, | )(${monthAlt})(?: (\\d{1,2}))?(?:, | )| )((?:19|20)\\d{2}[a-z]{0,2})$`);
206
+ const suffix = new RegExp(`(?:(, | )(${monthAlt})(?: (\\d{1,2}))?(?:, | )|, | )((?:19|20)\\d{2}[a-z]{0,2})$`);
204
207
  const m = suffix.exec(body);
205
208
  if (m === null) {
206
209
  return new IeeeDraft({ version: body, iso_stage: isoStage, iso_iteration: isoIteration });
@@ -216,7 +219,7 @@ export class IeeeDraft {
216
219
  });
217
220
  }
218
221
  render() {
219
- let result = `/D${this.version}`;
222
+ let result = `/D${this.version ?? ""}`;
220
223
  if (this.isoStage !== undefined)
221
224
  result += `=${this.isoStage}`;
222
225
  if (this.isoStage !== undefined && this.isoIteration !== undefined) {
@@ -225,6 +228,15 @@ export class IeeeDraft {
225
228
  if (this.revision !== undefined)
226
229
  result += `.${this.revision}`;
227
230
  if (this.year !== undefined) {
231
+ if (this.isoStage !== undefined) {
232
+ // The D= designator face dash-joins its date in IEEE's
233
+ // convention: the year, and a draft month rides the
234
+ // designator zero-padded ("D=FDIS-201008", pubid#203).
235
+ result += `-${this.year}`;
236
+ if (this.month !== undefined)
237
+ result += this.month.padStart(2, "0");
238
+ return result;
239
+ }
228
240
  const displayMonth = this.originalMonth;
229
241
  if (displayMonth !== undefined) {
230
242
  const useCommaBefore = this.commaBeforeMonthSet
@@ -236,7 +248,10 @@ export class IeeeDraft {
236
248
  result += displayMonth.length <= 4 || displayMonth === "Sept" ? ` ${this.year}` : `, ${this.year}`;
237
249
  }
238
250
  else {
239
- result += ` ${this.year}`;
251
+ // The year-only date joins with its comma - the grammar's
252
+ // comma-year draft tail (pubid-grammar 17244fc) - so the
253
+ // render re-parses through the same clause.
254
+ result += `, ${this.year}`;
240
255
  }
241
256
  }
242
257
  return result;
@@ -515,7 +530,8 @@ export function renderIeeeBase(id) {
515
530
  // spelling ("IEEE P15026.1, 05 2014" renders "P15026.1-2014-05").
516
531
  const yearOnCode = id.year !== undefined && id.edition === undefined &&
517
532
  (id.month === undefined || gluedMonth) &&
518
- (draftObj === undefined || draftObj.year !== undefined);
533
+ (draftObj === undefined || draftObj.year !== undefined ||
534
+ (draftObj.version ?? "").match(/-\d{4}-\d{2}$/) !== null);
519
535
  if (yearOnCode) {
520
536
  result += `-${id.year}`;
521
537
  }
@@ -575,7 +591,8 @@ export function renderIeeeBase(id) {
575
591
  result += ` ${id.year}`;
576
592
  }
577
593
  else if (id.year !== undefined && draftObj !== undefined && id.edition === undefined &&
578
- draftObj?.year === undefined && !iecIeeOnly) {
594
+ draftObj?.year === undefined &&
595
+ (draftObj.version ?? "").match(/-\d{4}-\d{2}$/) === null && !iecIeeOnly) {
579
596
  // A year-only date trailing the draft ("ANSI N42.34/D9a, 2015")
580
597
  // keeps its comma form instead of being silently dropped; a year
581
598
  // whose draft carries its own date dash-attached to the code above
@@ -882,8 +899,12 @@ export const ProjectDraftIdentifierClass = ieeeClass({ kind: "project-draft-iden
882
899
  codeColumns: true,
883
900
  extraDefs: {
884
901
  project_marker: { type: "boolean", default: false },
902
+ // The ISO-led joint project drafts carry the designator as the
903
+ // stage ("ISO/IEC/IEEE P42010/D4, 2019" - stage D4), the same
904
+ // key the ruby identity serializes.
905
+ stage: { type: "string" },
885
906
  },
886
- extraMappings: keyValue({ wire: "project_marker", to: "project_marker" }),
907
+ extraMappings: keyValue({ wire: "project_marker", to: "project_marker" }, { wire: "stage", to: "stage" }),
887
908
  });
888
909
  export const SiStandardClass = ieeeClass({ kind: "si-standard" }, {
889
910
  codeColumns: true,
@@ -1102,28 +1123,35 @@ export const JointDevelopmentClass = ieeeClass({ kind: "joint-development" }, {
1102
1123
  const isoStage = id["iso_stage"];
1103
1124
  const ieeeDraft = id["ieee_draft"];
1104
1125
  const codeObj = id.codeObj;
1105
- // ISO-led renders the ISO format ALWAYS (the stage-less published
1126
+ // ISO-led renders the ISO format (the stage-less published
1106
1127
  // form "ISO/IEC/IEEE 12207.2:2020" prints colon-year, no stage).
1107
1128
  // IEEE semantics: P = project (a draft); no P = a standard. The
1108
1129
  // P-state is identity-bearing and prints as spelled - it is never
1109
1130
  // added or stripped here.
1110
- if (leadParty === "ISO") {
1111
- // A D= designator row ("P26511/D8=DDIS.3") prints the designator
1112
- // after the code, not the stage as a word; only genuine stage
1113
- // rows print the stage position.
1114
- const designatorDraft = id["ieee_draft"];
1115
- const printsDesignator = designatorDraft !== undefined && /^D\d*=/.test(designatorDraft);
1131
+ // A stage-tracked D= designator is IEEE draft notation — its
1132
+ // canonical face is the designator spelling (§1.3 spelling 7),
1133
+ // whatever the lead party (the reference's canonical_format).
1134
+ if (leadParty === "ISO" && !(ieeeDraft ?? "").startsWith("D=")) {
1135
+ // The stage word is the ISO format's own position convention —
1136
+ // it prints whenever the identifier carries one, as stored,
1137
+ // iteration included ("DIS2", "CD4" — the lossless face,
1138
+ // pubid#477). A stage-tracked D= designator decomposes into its
1139
+ // stage word and date ("D=CD-2020" → "CD", 2020); the P project
1140
+ // stage is IEEE convention and never prints here.
1141
+ let stageWord = isoStage ?? "";
1142
+ let draftYear;
1143
+ if (stageWord === "" && ieeeDraft !== undefined) {
1144
+ const decomposed = /^D=([A-Z]+)(?:\.(\d+[a-z]?))?(?:[-:](\d{4}))?$/.exec(ieeeDraft);
1145
+ if (decomposed !== null) {
1146
+ stageWord = decomposed[1] ?? "";
1147
+ draftYear = decomposed[3];
1148
+ }
1149
+ }
1116
1150
  const parts = [];
1117
1151
  if (publishers !== undefined && publishers.length > 0)
1118
1152
  parts.push(publishers.join("/"));
1119
- if (!printsDesignator) {
1120
- if (id.typed_stage !== undefined) {
1121
- parts.push(id.typed_stage.record.iso_stage_equivalent ?? id.typed_stage.render());
1122
- }
1123
- else if (isoStage !== undefined) {
1124
- parts.push(isoStage);
1125
- }
1126
- }
1153
+ if (stageWord !== "")
1154
+ parts.push(stageWord);
1127
1155
  let codeStr = codeObj?.render() ?? "";
1128
1156
  // A P-prefixed joint project prints its parts dot-joined on the
1129
1157
  // ISO face ("ISO/IEEE DIS P15500.1"), whatever separator the
@@ -1132,15 +1160,23 @@ export const JointDevelopmentClass = ieeeClass({ kind: "joint-development" }, {
1132
1160
  codeStr = codeStr.replaceAll("-", ".");
1133
1161
  if (codeStr !== "")
1134
1162
  parts.push(codeStr);
1163
+ // The ISO-face date, in the spelling that parses back to the
1164
+ // same year and month: the colon year (":2018"); a numeric
1165
+ // month glued with dashes ("-2018-05"); a text month as
1166
+ // ", February 2015".
1135
1167
  let result = parts.join(" ");
1136
- if (id.year !== undefined)
1137
- result += `:${id.year}`;
1138
- // The joint stage-draft clause ("D=WD.5") rides after the year in
1139
- // the ISO-led print; only the bare language/edition marker ("(E)")
1140
- // prints - a relationship narrative is metadata, not identity.
1141
- const jointDraft = id["ieee_draft"];
1142
- if (jointDraft !== undefined && /^D\d*=/.test(jointDraft))
1143
- result += `/${jointDraft}`;
1168
+ const dateYear = id.year ?? draftYear;
1169
+ if (dateYear !== undefined) {
1170
+ if (id.month === undefined) {
1171
+ result += `:${dateYear}`;
1172
+ }
1173
+ else if (/^\d+$/.test(id.month)) {
1174
+ result += `-${dateYear}-${id.month.padStart(2, "0")}`;
1175
+ }
1176
+ else {
1177
+ result += `, ${id.month} ${dateYear}`;
1178
+ }
1179
+ }
1144
1180
  const marker = id["parenthetical_content"];
1145
1181
  if (marker !== undefined && /^[A-Z](?:\s*[/&]\s*[A-Z])*$/.test(marker)) {
1146
1182
  result += ` (${marker})`;
@@ -1158,16 +1194,36 @@ export const JointDevelopmentClass = ieeeClass({ kind: "joint-development" }, {
1158
1194
  // The P-state prints as spelled (P = project draft; no P =
1159
1195
  // standard). Never added or stripped.
1160
1196
  let codeStr = codeObj?.render() ?? "";
1161
- if (ieeeDraft !== undefined)
1197
+ let designatorCarriesYear = false;
1198
+ if (ieeeDraft !== undefined && ieeeDraft.startsWith("D=") && id.year !== undefined) {
1199
+ // The ordinal-less stage draft's canonical face (the
1200
+ // UpdateCodes rewrite): the publication year colon-joins the
1201
+ // code and the designator trails — "P16326:2017/D=WD.5".
1202
+ codeStr += `:${id.year}`;
1203
+ codeStr += `/${ieeeDraft}`;
1204
+ designatorCarriesYear = true;
1205
+ }
1206
+ else if (ieeeDraft !== undefined) {
1162
1207
  codeStr += `/${ieeeDraft}`;
1163
- else if (id.typed_stage?.ieeeDraftEquivalent !== undefined) {
1208
+ }
1209
+ else if (isoStage !== undefined) {
1210
+ // The ordinal-less stage draft: an iteration glued onto the
1211
+ // printed word ("CD2") renders in the doctrine's ".iter" slot
1212
+ // ("D=CD.2" — docs/IEEE-DRAFT-STAGES.md §1.3).
1213
+ const glued = /([A-Z]+?)(\d+)/.exec(isoStage);
1214
+ codeStr += glued !== null ? `/D=${glued[1]}.${glued[2]}` : `/D=${isoStage}`;
1215
+ }
1216
+ else if (id.typed_stage?.ieeeDraftEquivalent !== undefined &&
1217
+ !codeStr.startsWith(id.typed_stage.ieeeDraftEquivalent)) {
1164
1218
  codeStr += `/${id.typed_stage.ieeeDraftEquivalent}`;
1165
1219
  }
1166
1220
  if (codeStr !== "")
1167
1221
  parts.push(codeStr);
1168
1222
  let result = parts.join(" ");
1169
- if (id.year !== undefined)
1223
+ if (id.year !== undefined && !designatorCarriesYear &&
1224
+ !(ieeeDraft ?? "").endsWith(`-${id.year}`)) {
1170
1225
  result += `-${id.year}`;
1226
+ }
1171
1227
  if (id.redline === true)
1172
1228
  result += " - Redline";
1173
1229
  return result;
@@ -259,6 +259,47 @@ function normalizeRevisionNotation(input) {
259
259
  cleaned = cleaned.replace(/(\/D-?[0-9A-Za-z.]+)\s+[Rr][Ee][Vv]\s*(\d+)/, "$1/R-$2");
260
260
  cleaned = cleaned.replace(/[-/_.]?\s?[Rr][Ee][Vv][-\s]?(\d+)(\/D-?[0-9A-Za-z.]+)/, "$2/R-$1");
261
261
  cleaned = cleaned.replace(/(\d)[-._]?\s?[Rr][Ee][Vv]\s*(\d+)\s*$/, "$1/R-$2");
262
+ // The joint IEEE/CSA catalogue spells the type word before the
263
+ // project number ("IEEE/CSA Draft P844.1/D2, Sept, 2016"): the P
264
+ // already carries the project semantics (docs/IEEE-DRAFT-STAGES.md
265
+ // §1.1), so the redundant word drops and the joint route parses.
266
+ cleaned = cleaned.replace(/IEEE\/CSA Draft (P[A-Z0-9])/g, "IEEE/CSA $1");
267
+ // The same redundant word rides the wider joint lists
268
+ // ("IEEE//ISO/IEC Draft P29119-2/D=DIS-201112"); the crawl also
269
+ // doubles the publisher slash (the 2026-10-14 ruling: the list
270
+ // normalizes to single slashes). Drop the word and collapse the
271
+ // slash before the joint route sees either - scoped to the D=
272
+ // stage-draft tail (the doubled-D and stage-month tails route
273
+ // elsewhere until the grammar carries them).
274
+ cleaned = cleaned.replace(/((?:IEEE|ISO|IEC)(?:\/(?:IEEE|ISO|IEC|CSA))+)\s+Draft\s+(P[A-Z0-9][A-Z0-9.-]*\/D=[A-Z])/g, "$1 $2");
275
+ cleaned = cleaned.replace(/(IEEE|ISO|IEC)\/\//g, "$1/");
276
+ // The lettered REV spellings glue to the number ("P1310Rev3") or
277
+ // carry an after-word separator ("REV.ma", "REV-ma"); the rev rules
278
+ // read a space or a bare word, so the boundary normalizes (the
279
+ // render keeps the glued face). The before-word separator the Rev
280
+ // doctrine carries ("-REVmc") is untouched.
281
+ cleaned = cleaned.replace(/(REV|Rev|rev)[.\-](?=[A-Za-z])/g, "$1");
282
+ cleaned = cleaned.replace(/(\d)(REV|Rev|rev)/g, "$1 $2");
283
+ // The corrigendum's own draft rides before the ordinal in the house
284
+ // order ("PC37.101-2006/D2, 2007/Cor. 1"): the catalogue spellings
285
+ // ".../Cor 1, D4, July 2015" and ".../Cor 1-201x/D5, April 2012"
286
+ // re-order into it, with the draft's date glued -YYYY-MM (pubid
287
+ // ruling 2026-10-14: no comma dates).
288
+ const COR_MONTHS = {
289
+ January: "01", February: "02", March: "03", April: "04", May: "05",
290
+ June: "06", July: "07", August: "08", September: "09", Sept: "09",
291
+ October: "10", November: "11", December: "12", Jan: "01", Feb: "02",
292
+ Mar: "03", Apr: "04", Jun: "06", Jul: "07", Aug: "08", Sep: "09",
293
+ Oct: "10", Nov: "11", Dec: "12",
294
+ };
295
+ cleaned = cleaned.replace(/\/Cor(?:rigendum)? (\d+(?:-[0-9xX]{4})?)\/(D[A-Z0-9][^,]*), ([A-Za-z]+) (\d{4})/g, (_m, cor, draft, mon, year) => {
296
+ const month = COR_MONTHS[mon.charAt(0).toUpperCase() + mon.slice(1).toLowerCase()];
297
+ return month === undefined ? _m : `/${draft}-${year}-${month}/Cor. ${cor}`;
298
+ });
299
+ cleaned = cleaned.replace(/\/Cor(?:rigendum)? (\d+), (D[A-Z0-9][^,]*), ([A-Za-z]+) (\d{4})/g, (_m, cor, draft, mon, year) => {
300
+ const month = COR_MONTHS[mon.charAt(0).toUpperCase() + mon.slice(1).toLowerCase()];
301
+ return month === undefined ? _m : `/${draft}-${year}-${month}/Cor. ${cor}`;
302
+ });
262
303
  // Lettered inline revisions are IDENTITY (pubid ruling 2026-10-14):
263
304
  // the grammar captures them natively with their separator; the former
264
305
  // strip rewrite here dropped the letters and is gone.
@@ -376,7 +417,11 @@ export function preprocessIeee(input) {
376
417
  cleaned = cleaned.replace(/^(ISO\/IEC \d+[-.]\d*)\s*:\s*(\d{4})/, "$1:$2");
377
418
  cleaned = cleaned.replace(/^(ISO\/IEC \d+)\s*:\s*(\d{4})/, "$1:$2");
378
419
  cleaned = cleaned.replace(/^(IEC\/IEEE P[\w.-]+)_D/, "$1/D");
379
- cleaned = cleaned.replace(/^IEEE\/ISO\/IEC\s+(P[\w.-]+)/, "ISO/IEC/IEEE $1");
420
+ // Scoped: the D= stage-draft rows keep the IEEE-led list (the ruby
421
+ // ieee_format routes them with the input publisher order and lead
422
+ // IEEE - "IEEE/ISO/IEC P29119-2/D=DIS-201112"); the other IEEE/ISO/IEC
423
+ // spellings print the ISO-led face.
424
+ cleaned = cleaned.replace(/^IEEE\/ISO\/IEC\s+(?!P[\w.-]+\/D=)(P[\w.-]+)/, "ISO/IEC/IEEE $1");
380
425
  cleaned = cleaned.replace(/^IEEE\/IEC\/ISO\s+(P[\w.-]+)/, "IEC/ISO/IEEE $1");
381
426
  cleaned = cleaned.replace(/^(IEEE\/IEC P[\w.-]+)\s+D(\d)/, "$1/D$2");
382
427
  cleaned = cleaned.replace(/^(IEEE\/IEC P[\w.-]+)\s+(CDV|FDIS|CD|DIS|ED\d)/, "$1/$2");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubid/pubid",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
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",
@@ -17,7 +17,7 @@
17
17
  "artifact": "tsc -p tsconfig.json && node scripts/build-artifact.mjs"
18
18
  },
19
19
  "dependencies": {
20
- "parsanol": "^0.10.3",
20
+ "parsanol": "^0.10.4",
21
21
  "yaml": "^2.5.0"
22
22
  },
23
23
  "devDependencies": {
@@ -1 +1 @@
1
- {"bindings":null,"version":"0.1.0","grammar":"PubidAdobe","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Entity":{"atom":48}},{"Entity":{"atom":30}},{"Entity":{"atom":9}},{"Str":{"pattern":"Adobe"}},{"Entity":{"atom":5}},{"Str":{"pattern":" "}},{"Str":{"pattern":"TN"}},{"Entity":{"atom":5}},{"Repetition":{"atom":7,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[3,4,6,8]}},{"Entity":{"atom":24}},{"Str":{"pattern":"Adobe"}},{"Entity":{"atom":5}},{"Str":{"pattern":"Technical"}},{"Entity":{"atom":5}},{"Str":{"pattern":"Note"}},{"Entity":{"atom":5}},{"Repetition":{"atom":16,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"#"}},{"Repetition":{"atom":18,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":5}},{"Repetition":{"atom":20,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[17,19,21]}},{"Repetition":{"atom":22,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[11,12,13,14,15,23]}},{"Entity":{"atom":29}},{"Str":{"pattern":"ATN"}},{"Entity":{"atom":5}},{"Repetition":{"atom":27,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[26,28]}},{"Alternative":{"atoms":[2,10,25]}},{"Entity":{"atom":32}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":31,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"number","atom":33}},{"Entity":{"atom":36}},{"Str":{"pattern":"."}},{"Entity":{"atom":42}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[_]"}},{"Alternative":{"atoms":[38,39,40,41]}},{"Repetition":{"atom":37,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"slug","atom":43}},{"Sequence":{"atoms":[35,44]}},{"Repetition":{"atom":45,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[1,34,46]}},{"Named":{"name":"tech_note","atom":47}},{"Entity":{"atom":53}},{"Entity":{"atom":32}},{"Repetition":{"atom":50,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"number","atom":51}},{"Named":{"name":"tech_note_bare","atom":52}},{"Entity":{"atom":87}},{"Str":{"pattern":"Adobe Publication "}},{"Repetition":{"atom":55,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":86}},{"Entity":{"atom":82}},{"Entity":{"atom":75}},{"Entity":{"atom":69}},{"Entity":{"atom":62}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Repetition":{"atom":61,"min":1,"max":null,"tag":"Repetition"}},{"Entity":{"atom":67}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Alternative":{"atoms":[65,66]}},{"Repetition":{"atom":64,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[63,68]}},{"Entity":{"atom":71}},{"Str":{"pattern":"-"}},{"Entity":{"atom":69}},{"Sequence":{"atoms":[70,72]}},{"Repetition":{"atom":73,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[60,74]}},{"Named":{"name":"slug","atom":59}},{"Entity":{"atom":71}},{"Entity":{"atom":32}},{"Repetition":{"atom":78,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"version","atom":79}},{"Sequence":{"atoms":[76,77,80]}},{"Named":{"name":"publication_version","atom":81}},{"Entity":{"atom":85}},{"Entity":{"atom":75}},{"Named":{"name":"slug","atom":84}},{"Alternative":{"atoms":[58,83]}},{"Sequence":{"atoms":[56,57]}},{"Alternative":{"atoms":[0,49,54]}}],"root":88},"bindings":[]}},"preprocess":{},"tables":{},"lint":{"order_warnings":["rule tech_note_prefix: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule tech_note_prefix: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule tech_note_prefix: branches 2 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule publication_slug_or_version: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule identifier: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)"]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"Adobe TN 15","expect":{}},{"entry":"identifier","kind":"accept","input":"ATN 30","expect":{}},{"entry":"identifier","kind":"accept","input":"Adobe Technical Note #12","expect":{}},{"entry":"identifier","kind":"reject","input":"###","expect":{}}],"docs":{},"source":"# Port of lib/pubid/adobe/parser.rb: tech notes (Adobe TN 5014, legacy\n# \"Adobe Technical Note #5014\", ATN5014, bare 5014) and publications\n# (adobe-glyph-list, adobe-japan1-7 with the version split tried first).\n\ngrammar PubidAdobe version \"0.1.0\" {\n tech_note_tn_prefix = \"Adobe\" space \"TN\" [space]\n tech_note_long_prefix = \"Adobe\" space \"Technical\" space \"Note\" [[space] [\"#\"] [space]]\n tech_note_atn_prefix = \"ATN\" [space]\n tech_note_prefix = tech_note_tn_prefix / tech_note_long_prefix / tech_note_atn_prefix\n # Capture precedence: (*rep) as name wraps the whole repetition, so a\n # digit run joins to one string instead of re-keying every character;\n # slug is captured exactly once (the bare form) or as a sibling of\n # version inside the publication_version capture.\n tech_note = (tech_note_prefix (1*digit) as number [(dot (1*slug_char) as slug)]) as tech_note\n bare_number = ((1*digit) as number) as tech_note_bare\n slug_segment = 1*lower1 *lowerd\n slug_text = slug_segment *(dash slug_segment)\n slug_inner = (slug_text) as slug\n publication_with_version = ((slug_text) as slug dash (1*digit) as version) as publication_version\n publication_slug_or_version = publication_with_version / slug_inner\n publication = [\"Adobe Publication \"] publication_slug_or_version\n identifier = tech_note / bare_number / publication\n slug_char = %x41-5A / %x61-7A / %x30-39 / %x5F\n lower1 = %x61-7A\n lowerd = %x61-7A / %x30-39\n digit = %x30-39\n space = \" \"\n dot = \".\"\n dash = \"-\"\n entry identifier: identifier\n\n test {\n accept \"Adobe TN 15\"\n accept \"ATN 30\"\n accept \"Adobe Technical Note #12\"\n reject \"###\"\n }\n}\n","checksum":"sha256:38e8488c3da1c0f4a36fe2f63d87fee551d9317c5dae62f56972da1efe842a52"}
1
+ {"bindings":null,"version":"0.1.0","grammar":"PubidAdobe","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Entity":{"atom":48}},{"Entity":{"atom":30}},{"Entity":{"atom":9}},{"Str":{"pattern":"Adobe"}},{"Entity":{"atom":5}},{"Str":{"pattern":" "}},{"Str":{"pattern":"TN"}},{"Entity":{"atom":5}},{"Repetition":{"atom":7,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[3,4,6,8]}},{"Entity":{"atom":24}},{"Str":{"pattern":"Adobe"}},{"Entity":{"atom":5}},{"Str":{"pattern":"Technical"}},{"Entity":{"atom":5}},{"Str":{"pattern":"Note"}},{"Entity":{"atom":5}},{"Repetition":{"atom":16,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"#"}},{"Repetition":{"atom":18,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":5}},{"Repetition":{"atom":20,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[17,19,21]}},{"Repetition":{"atom":22,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[11,12,13,14,15,23]}},{"Entity":{"atom":29}},{"Str":{"pattern":"ATN"}},{"Entity":{"atom":5}},{"Repetition":{"atom":27,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[26,28]}},{"Alternative":{"atoms":[2,10,25]}},{"Entity":{"atom":32}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":31,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"number","atom":33}},{"Entity":{"atom":36}},{"Str":{"pattern":"."}},{"Entity":{"atom":42}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[_]"}},{"Alternative":{"atoms":[38,39,40,41]}},{"Repetition":{"atom":37,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"slug","atom":43}},{"Sequence":{"atoms":[35,44]}},{"Repetition":{"atom":45,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[1,34,46]}},{"Named":{"name":"tech_note","atom":47}},{"Entity":{"atom":53}},{"Entity":{"atom":32}},{"Repetition":{"atom":50,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"number","atom":51}},{"Named":{"name":"tech_note_bare","atom":52}},{"Entity":{"atom":87}},{"Str":{"pattern":"Adobe Publication "}},{"Repetition":{"atom":55,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":86}},{"Entity":{"atom":82}},{"Entity":{"atom":75}},{"Entity":{"atom":69}},{"Entity":{"atom":62}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Repetition":{"atom":61,"min":1,"max":null,"tag":"Repetition"}},{"Entity":{"atom":67}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Alternative":{"atoms":[65,66]}},{"Repetition":{"atom":64,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[63,68]}},{"Entity":{"atom":71}},{"Str":{"pattern":"-"}},{"Entity":{"atom":69}},{"Sequence":{"atoms":[70,72]}},{"Repetition":{"atom":73,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[60,74]}},{"Named":{"name":"slug","atom":59}},{"Entity":{"atom":71}},{"Entity":{"atom":32}},{"Repetition":{"atom":78,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"version","atom":79}},{"Sequence":{"atoms":[76,77,80]}},{"Named":{"name":"publication_version","atom":81}},{"Entity":{"atom":85}},{"Entity":{"atom":75}},{"Named":{"name":"slug","atom":84}},{"Alternative":{"atoms":[58,83]}},{"Sequence":{"atoms":[56,57]}},{"Alternative":{"atoms":[0,49,54]}}],"root":88},"bindings":[]}},"preprocess":{},"tables":{},"lint":{"order_warnings":["rule tech_note_prefix: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule tech_note_prefix: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule tech_note_prefix: branches 2 and 3 are order-dependent (shared first bytes; ordered choice is decisive)","rule publication_slug_or_version: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)","rule identifier: branches 1 and 3 are order-dependent (shared first bytes; ordered choice is decisive)"]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"Adobe TN 15","expect":{}},{"entry":"identifier","kind":"accept","input":"ATN 30","expect":{}},{"entry":"identifier","kind":"accept","input":"Adobe Technical Note #12","expect":{}},{"entry":"identifier","kind":"reject","input":"###","expect":{}}],"docs":{},"source":"# Port of lib/pubid/adobe/parser.rb: tech notes (Adobe TN 5014, legacy\n# \"Adobe Technical Note #5014\", ATN5014, bare 5014) and publications\n# (adobe-glyph-list, adobe-japan1-7 with the version split tried first).\n\ngrammar PubidAdobe version \"0.1.0\" {\n tech_note_tn_prefix = \"Adobe\" space \"TN\" [space]\n tech_note_long_prefix = \"Adobe\" space \"Technical\" space \"Note\" [[space] [\"#\"] [space]]\n tech_note_atn_prefix = \"ATN\" [space]\n tech_note_prefix = tech_note_tn_prefix / tech_note_long_prefix / tech_note_atn_prefix\n # Capture precedence: (*rep) as name wraps the whole repetition, so a\n # digit run joins to one string instead of re-keying every character;\n # slug is captured exactly once (the bare form) or as a sibling of\n # version inside the publication_version capture.\n tech_note = (tech_note_prefix (1*digit) as number [(dot (1*slug_char) as slug)]) as tech_note\n bare_number = ((1*digit) as number) as tech_note_bare\n slug_segment = 1*lower1 *lowerd\n slug_text = slug_segment *(dash slug_segment)\n slug_inner = (slug_text) as slug\n publication_with_version = ((slug_text) as slug dash (1*digit) as version) as publication_version\n publication_slug_or_version = publication_with_version / slug_inner\n publication = [\"Adobe Publication \"] publication_slug_or_version\n identifier = tech_note / bare_number / publication\n slug_char = %x41-5A / %x61-7A / %x30-39 / %x5F\n lower1 = %x61-7A\n lowerd = %x61-7A / %x30-39\n digit = %x30-39\n space = \" \"\n dot = \".\"\n dash = \"-\"\n entry identifier: identifier\n\n test {\n accept \"Adobe TN 15\"\n accept \"ATN 30\"\n accept \"Adobe Technical Note #12\"\n reject \"###\"\n }\n}\n","source_hash":"bba8ad76fa85bcbf6363b8bfa1502a76a050fa4ca87bcf5a8bfd14a19cab9f5e","compiler_version":"1.3.93","checksum":"sha256:cef9c127759ddb05f7bd725d9bc923b6207c099dbd8a8e73ec4ae84101040361"}