@pubid/pubid 0.1.37 → 0.1.39

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,6 @@
1
1
  import { match, ref, str } from "../../grammar/engine.js";
2
2
  import { stageTokens } from "./model.js";
3
+ import { ISO_TYPED_STAGES } from "./stages.generated.js";
3
4
  /**
4
5
  * Port of lib/pubid/iso/parser.rb. The identifier alternation order is
5
6
  * load-bearing (PEG ordered choice): nsb_iso | tc_document |
@@ -9,6 +10,11 @@ import { stageTokens } from "./model.js";
9
10
  * supplement tables exactly like the Ruby TYPED_STAGES constants.
10
11
  */
11
12
  const SUPPLEMENT_TYPE_KEYS = ["amd", "cor", "suppl", "ext", "add"];
13
+ /** The DAM-family ballot stage codes (grammar table
14
+ * typed_stages_ballots: PDAM, DAM, DAmd, FDAM, FDAmd, FPDAM — exactly
15
+ * these entries' abbrs); the TYPE-first ballot branch stays scoped to
16
+ * the ballot family. */
17
+ export const BALLOT_STAGE_CODES = ["pdam", "damd", "fdamd", "fpdam"];
12
18
  const BASE_STAGE_TOKENS = stageTokens().filter((t) => {
13
19
  // Supplement tokens are excluded from the base table by type key, but
14
20
  // stageTokens() is type-agnostic here; the split is done via the
@@ -208,7 +214,68 @@ function buildRules() {
208
214
  .as("base")
209
215
  .then(space.maybe(), str("|"), space.maybe(), rules["idf_identifier"])
210
216
  .as("joint_identifier"));
211
- rule("identifier", () => rules["bare_tc_document"].or(rules["nsb_iso_identifier"], rules["tc_document"], rules["directives_identifiers"], rules["jcgm_identifier"], rules["iso_r_supplement_identifier"], rules["iso_r_identifier"], rules["supplement_supplement_supplement_identifier"], rules["supplement_supplement_identifier"], rules["supplement_identifier"], rules["joint_identifier"], rules["identifier_copublishers"]));
217
+ // Historic TYPE-first ballot faces (decode-to-modern, owner ruling
218
+ // 2026-11-12): the DAM-family token in TYPE position — "ISO/IEC FDAM 1
219
+ // 14496-12:2004", "ISO/PDAM 19973-2" — and the space-attached ballot
220
+ // face "ISO 15877-5:2009 FDAM 1". Both branches sit LAST in the
221
+ // identifier alternation, so they are purely additive: every face an
222
+ // earlier branch parses keeps its exact parse. The ballot token
223
+ // captures as ballot_type_with_stage; a number on the TYPE token is
224
+ // space-bounded (ballot_number) so "PDAM 19973-2" keeps 19973 in the
225
+ // base; an on-base stamp's number/date capture as ballot_stamp_* (the
226
+ // stamp token itself matches uncaptured — the ballot's stage comes
227
+ // from the TYPE token and the decode keeps no spelling). The builder
228
+ // folds the captures into ONE amendment layer over the whole base, the
229
+ // number taken from the TYPE token, the stamp, or the implied 1.
230
+ const ballotStageTokens = ISO_TYPED_STAGES
231
+ .filter((st) => BALLOT_STAGE_CODES.includes(st.code))
232
+ .flatMap((st) => st.abbr);
233
+ const ballotTypeAlt = literalAlternation(ballotStageTokens).as("ballot_type_with_stage");
234
+ rule("ballot_type_with_stage", () => ballotTypeAlt);
235
+ // An on-base stamp riding a ballot face ("/Amd 9:2001", "/Amd.1",
236
+ // "/Am1:2006"): it carries the amendment's number and ballot date
237
+ // while its own token letters are decode-away (matched uncaptured).
238
+ // The stamp's date captures under its own ballot_stamp_* names —
239
+ // reusing the base date capture would collide with second_part's at
240
+ // the sequence merge and the stamp's year would eat the base's.
241
+ rule("ballot_stamp", () => str("/")
242
+ .then(literalAlternation(supplementTypeTokens))
243
+ .then(space
244
+ .maybe()
245
+ .then(digit.repeat(1, 5).as("ballot_stamp_number"))
246
+ .then(str(":")
247
+ .or(dash)
248
+ .then(yearDigits
249
+ .then(dash.then(monthDigits).maybe(), dash.then(dayDigits).maybe())
250
+ .as("ballot_stamp_date"))
251
+ .or(str(":")
252
+ .or(dash)
253
+ .then(str("--").or(str("—")).as("ballot_stamp_undated_marker")))
254
+ .maybe())
255
+ .or(str(".").then(digits.as("ballot_stamp_dot_number")))));
256
+ // "ISO/IEC FDAM 1 14496-12:2004", "ISO/PDAM 19973-2": the ballot
257
+ // token first, then the base kept whole. The TYPE-token number is
258
+ // space-bounded on both sides — mandatory trailing space — so
259
+ // "PDAM 19973-2" cannot read 19973 as the ballot number: with no
260
+ // second space the group backtracks and 19973 parses in second_part.
261
+ rule("ballot_supplement_identifier", () => rules["prefix_with_copublishers"]
262
+ .then(space.maybe(), str("/").maybe(), ballotTypeAlt, space.then(digit.repeat(1, Infinity).as("ballot_number")).then(space).maybe(), space.maybe(), rules["type_with_stage"].maybe(), space.maybe(), rules["second_part"], rules["ballot_stamp"].maybe()));
263
+ // The space-attached ballot face: "ISO 15877-5:2009 FDAM 1" — the
264
+ // base designation first, then the ballot token attached by a space.
265
+ // The base front is inlined (not identifier_copublishers_no_third):
266
+ // that rule ends in third_part_edition, whose own optional space
267
+ // would consume the separator the ballot token needs. No base
268
+ // edition is served here — none of the stored or owner-cited ballot
269
+ // faces carries one.
270
+ rule("ballot_space_supplement_identifier", () => rules["guide_prefix"]
271
+ .as("type_with_stage_fr")
272
+ .then(space)
273
+ .maybe()
274
+ .then(rules["prefix_with_copublishers"].maybe(), space.maybe(), str("/").maybe(), rules["type_with_stage"].maybe(), space.maybe(), rules["second_part"], space, ballotTypeAlt, space.then(digit.repeat(1, Infinity).as("ballot_number")).maybe()));
275
+ rule("identifier", () => rules["bare_tc_document"].or(rules["nsb_iso_identifier"], rules["tc_document"], rules["directives_identifiers"], rules["jcgm_identifier"], rules["iso_r_supplement_identifier"], rules["iso_r_identifier"], rules["supplement_supplement_supplement_identifier"], rules["supplement_supplement_identifier"], rules["supplement_identifier"], rules["joint_identifier"], rules["identifier_copublishers"],
276
+ // The historic ballot faces go last — purely additive branches
277
+ // (an input an earlier member parses keeps that parse).
278
+ rules["ballot_supplement_identifier"], rules["ballot_space_supplement_identifier"]));
212
279
  rule("root", () => rules["identifier"]);
213
280
  return rules;
214
281
  }
@@ -1,6 +1,6 @@
1
1
  import { ParseFailed, parseGrammar } from "../../grammar/engine.js";
2
2
  import { PubidDate, Language, Iteration } from "../../model/component.js";
3
- import { isoGrammar } from "./grammar.js";
3
+ import { isoGrammar, BALLOT_STAGE_CODES } from "./grammar.js";
4
4
  import { IsoBundledIdentifier, IsoDirectives, IsoDirectivesSupplement, IsoEdition, IsoPublisher, IsoSupplementIdentifier, IsoTcDocument, isoClassForType, locateStage, } from "./model.js";
5
5
  import { ISO_TYPED_STAGES } from "./stages.generated.js";
6
6
  /**
@@ -232,6 +232,15 @@ class IsoBuilder {
232
232
  }
233
233
  buildStandard(treeIn) {
234
234
  const tree = { ...treeIn };
235
+ // Historic TYPE-first ballot face ("ISO/IEC FDAM 1 14496-12:2004",
236
+ // "ISO/PDAM 19973-2", "ISO 15877-5:2009 FDAM 1"): the ballot_* keys
237
+ // are one amendment layer over the whole base, never a second
238
+ // supplement layer (the stored twins keep one level), so the layer
239
+ // is assembled here, before the supplement branch could read the
240
+ // ballot token as an outer wrapper.
241
+ if (tree["ballot_type_with_stage"] !== undefined) {
242
+ return this.buildBallotSupplement(tree);
243
+ }
235
244
  // Legacy ISO/R: publisher ISO + type "R".
236
245
  if (tree["iso_r_prefix"] !== undefined) {
237
246
  tree["publisher"] = "ISO";
@@ -280,8 +289,63 @@ class IsoBuilder {
280
289
  attrs["base"] = this.build(tree["base"]);
281
290
  }
282
291
  const identifier = new klass(attrs);
292
+ // The owner's no-number rule, applied on every construction path
293
+ // that routes through this builder (parse, URN, bundle members): a
294
+ // DAM-family ballot-stage amendment token with no number anywhere
295
+ // names Amendment 1 ("ISO 19973-2/PDAM", "ISO/IEC PDAM 12207"). The
296
+ // number is identity-bearing, so the default lands at build time and
297
+ // the wire hash serializes it — parse and fromHash(toHash) agree
298
+ // because both carry the filled number. The ballot stages are
299
+ // matched by the typed stage's code (the per-typed-stage key) —
300
+ // PDAM's stageCode is "cd", the harmonized range it lives in.
301
+ if (typeKey === "amd" && attrs["number"] === undefined &&
302
+ BALLOT_STAGE_CODES.includes(attrs["typedStageState"]?.entry.code ?? "")) {
303
+ identifier["number"] = "1";
304
+ }
283
305
  return identifier;
284
306
  }
307
+ /**
308
+ * Historic TYPE-first ballot face (owner ruling 2026-11-12, Option B
309
+ * decode-to-modern): "[P|F]?DAM [N] BASE [stamp]" decodes to ONE
310
+ * amendment over the WHOLE base, rendered as the canonical on-base
311
+ * slash face. The stage comes from the ballot token; the number from
312
+ * the TYPE token, then the on-base stamp, then the owner's no-number
313
+ * rule (a ballot face with no number anywhere names Amendment 1); the
314
+ * stamp's date is the ballot supplement's date. The base keeps every
315
+ * component it carried: a legacy slash-part stays a part of the base
316
+ * number ("ISO/IEC FPDAM 9075/1" → base "9075-1"), and the base's own
317
+ * type token stays the base's ("ISO/IEC PDAM TR 9573-11" → base
318
+ * "TR 9573-11") — nothing migrates into the supplement slot. The
319
+ * stamp's token letters (Amd/Am/…) are decode-away: the ballot's
320
+ * stage is the TYPE token's.
321
+ */
322
+ buildBallotSupplement(tree) {
323
+ const fields = {
324
+ type_with_stage: s(tree["ballot_type_with_stage"]),
325
+ };
326
+ const number = s(tree["ballot_number"]) ??
327
+ s(tree["ballot_stamp_number"]) ??
328
+ s(tree["ballot_stamp_dot_number"]) ??
329
+ "1";
330
+ fields["number_with_part"] = number;
331
+ // The on-base stamp: its date (or undated marker) is the ballot
332
+ // supplement's; its number feeds the precedence above; its token
333
+ // letters were matched uncaptured and decode away.
334
+ const stampDate = s(tree["ballot_stamp_date"]);
335
+ if (stampDate !== undefined) {
336
+ const [year, month, day] = stampDate.split("-");
337
+ fields["date"] = new PubidDate({ year, month, day });
338
+ }
339
+ else if (tree["ballot_stamp_undated_marker"] !== undefined) {
340
+ fields["date"] = new PubidDate({ undated: true });
341
+ }
342
+ const rest = {};
343
+ for (const [key, value] of Object.entries(tree)) {
344
+ if (!key.startsWith("ballot_"))
345
+ rest[key] = value;
346
+ }
347
+ return this.build({ ...fields, base: rest });
348
+ }
285
349
  buildDirectives(tree, state) {
286
350
  const attrs = this.baseAttrs(tree);
287
351
  const publisher = this.publisherOf(tree);
@@ -102,8 +102,14 @@ export class IsoIdentifier extends BaseIdentifier {
102
102
  const ts = this.typedStageState;
103
103
  if (ts === undefined)
104
104
  return false;
105
- if (ts.entry.longAbbr !== undefined && ts.originalAbbr?.startsWith(ts.entry.longAbbr))
105
+ if (ts.entry.longAbbr !== undefined && ts.originalAbbr !== undefined &&
106
+ (ts.originalAbbr.startsWith(ts.entry.longAbbr) ||
107
+ // The shorter long-family spellings ("Am", the alias of "Amd"
108
+ // the owner ruled renders canonical) are prefixes of the long
109
+ // abbr; the short family ("AMD") matches neither direction.
110
+ ts.entry.longAbbr.startsWith(ts.originalAbbr))) {
106
111
  return true;
112
+ }
107
113
  if (ts.entry.longAbbr !== undefined && ts.originalAbbr?.includes("Directives"))
108
114
  return true;
109
115
  return false;
@@ -32,7 +32,7 @@ export const ISO_TYPED_STAGES = [
32
32
  code: "prf_amd", stageCode: "prf", typeCode: "amd", abbr: ["PRF Amd"], harmonized: ["50.00"]
33
33
  }),
34
34
  TS("amd", {
35
- code: "published", stageCode: "published", typeCode: "amd", abbr: ["Amd", "AMD", "Amd."], shortAbbr: "AMD", longAbbr: "Amd", harmonized: ["60.00", "60.60"]
35
+ code: "published", stageCode: "published", typeCode: "amd", abbr: ["Amd", "AMD", "Amd.", "Am"], shortAbbr: "AMD", longAbbr: "Amd", harmonized: ["60.00", "60.60"]
36
36
  }),
37
37
  TS("cor", {
38
38
  code: "pwi_cor", stageCode: "proposal", typeCode: "cor", abbr: ["PWI Cor"], harmonized: ["00.00", "00.20", "00.60", "00.92", "00.93", "00.98", "00.99"]
@@ -180,6 +180,18 @@ function locateIdentifierKlass(h) {
180
180
  return classForKind("dated-document");
181
181
  }
182
182
  let series = s(h["series"]);
183
+ // The default-series plain-number branch (pubid#711): the grammar's
184
+ // trailing alternatives accept a bare report number ("800-90B") and
185
+ // its publisher-led spelling ("NIST 800-90B") with NO series token.
186
+ // The SP series is the default: the docid-derived citation of
187
+ // NIST.SP.800-90B is exactly the bare docnumber "800-90B", and the
188
+ // Builder fills the same SP series the spelled form carries, so the
189
+ // two spellings are one identity to a lookup. No other branch can
190
+ // arrive here series-less (dated shapes returned above; every
191
+ // series-bearing branch captures :series).
192
+ if (series === undefined) {
193
+ return classForKind("special-publication");
194
+ }
183
195
  if (series?.startsWith("NBS ") === true || series?.startsWith("NBS.") === true || series?.startsWith("NIST.") === true) {
184
196
  const separator = series.startsWith("NBS ") ? "NBS " : series.startsWith("NBS.") ? "NBS." : "NIST.";
185
197
  let simple = series.slice(separator.length);
@@ -857,6 +869,21 @@ class NistBuilder {
857
869
  const klass = locateIdentifierKlass(h);
858
870
  const policy = seriesPolicy(s(h["series"]));
859
871
  const identifier = new klass();
872
+ // The default-series plain-number branch (pubid#711): the grammar's
873
+ // trailing alternatives carry no series token ("800-90B" /
874
+ // "NIST 800-90B"), so the identity gains the class's default series
875
+ // and parses == to the spelled "NIST SP 800-90B" form. The bare
876
+ // publisher rides the series-default-publisher fill at the tail
877
+ // (the #704 shape: filled after publisher_was_parsed is recorded,
878
+ // so the bare face keeps rendering bare). The policy stays the
879
+ // series-less Base one — Ruby's Series.for reads parsed_hash, not
880
+ // the filled attribute (SP's own policy is Base anyway).
881
+ if (h["series"] === undefined) {
882
+ const defaultSeries = klass.nistDefaultSeries;
883
+ if (defaultSeries !== undefined) {
884
+ identifier["series"] = new NistCode({ value: defaultSeries });
885
+ }
886
+ }
860
887
  const attrs = {};
861
888
  const toEdition = (v) => {
862
889
  if (isObj(v)) {
@@ -1123,16 +1150,19 @@ class NistBuilder {
1123
1150
  self["supplement"] = NistSupplement.fromRaw(supp.value, supp.hasRevision);
1124
1151
  }
1125
1152
  }
1126
- // Default publisher for series-less renders.
1153
+ // apply_series_default_publisher (the #704 rule): a series that
1154
+ // implies its publisher (bare "FIPS 197" is "NIST FIPS 197"; bare
1155
+ // "800-90B" is "NIST SP 800-90B" — pubid#712) gets it now, after the
1156
+ // publisher_was_parsed: false above, so the two spellings are ==
1157
+ // while the bare face keeps rendering bare (the printed-publisher
1158
+ // views read the flag, not the filled value). Runs only when the
1159
+ // publisher is absent AND unspelled — at this point that is exactly
1160
+ // `publisher === undefined` (the flag was recorded false above iff
1161
+ // no publisher was parsed).
1127
1162
  if (self["publisher"] === undefined) {
1128
1163
  const defaultPublisher = identifier.constructor.nistDefaultPublisher;
1129
1164
  if (defaultPublisher !== undefined && defaultPublisher !== "") {
1130
1165
  self["publisher"] = defaultPublisher;
1131
- if (self["publisher_was_parsed"] === false) {
1132
- // Circular/RPT/MP render "NBS <series>" through the default even
1133
- // when not parsed — keep the flag false (Ruby publisher_was_parsed
1134
- // stays false but to_short_style uses default only via series).
1135
- }
1136
1166
  }
1137
1167
  }
1138
1168
  return identifier;
@@ -680,6 +680,11 @@ function nistClass(spec) {
680
680
  class NistConcrete extends (spec.supplementSeam === true ? NistSupplementIdentifier : NistIdentifier) {
681
681
  static polymorphicName = `pubid:nist:${spec.kind}`;
682
682
  static urnGenerator = NistUrnGenerator;
683
+ /** Ruby `self.default_series` (pubid#711) — read by the Builder. */
684
+ static nistDefaultSeries = spec.defaultSeries;
685
+ /** Ruby `self.series_default_publisher` (#704/#712) — read by the
686
+ * Builder's series-default-publisher fill. */
687
+ static nistDefaultPublisher = spec.seriesDefaultPublisher;
683
688
  seriesCode() {
684
689
  return spec.seriesCode;
685
690
  }
@@ -709,8 +714,24 @@ function nistClass(spec) {
709
714
  return `${this.publisher ?? "NIST"} ${this.date_year ?? ""}-${this.date_month ?? ""}-${this.date_day ?? ""} ${this.dated_seq ?? ""}`;
710
715
  }
711
716
  if (spec.style === "fips") {
712
- let result = this.publisher !== undefined ? `${this.publisher} ` : "";
713
- result += this.seriesCode() ?? "";
717
+ // The official published face is "FIPS PUB <number>": FIPS is
718
+ // government-published and "PUB" (Federal Information Processing
719
+ // Standards PUBlication) is part of the official designation
720
+ // (owner ruling 2026-11-12). A spelled NIST publisher renders the
721
+ // official face; the historical NBS-prefixed rows keep
722
+ // "NBS FIPS <number>", and the bare publisher-less spelling keeps
723
+ // its short "FIPS <number>" face. The lead reads
724
+ // shortRenderPublisher() (the printed-publisher view), so a
725
+ // series-default publisher filled without being spelled (#704's
726
+ // ruby model) can never flip the bare face; the tail rides the
727
+ // lead.
728
+ const pub = this.shortRenderPublisher();
729
+ const series = this.seriesCode() ?? "";
730
+ let result = pub === "NIST"
731
+ ? "FIPS PUB"
732
+ : pub !== undefined && pub !== ""
733
+ ? `${pub} ${series}`
734
+ : series;
714
735
  let numberPart = this.number ?? "";
715
736
  const fy = /^(\d{1,3})-(\d{4})$/.exec(numberPart);
716
737
  if (fy !== null) {
@@ -803,7 +824,17 @@ export class NistSupplementIdentifier extends NistIdentifier {
803
824
  }
804
825
  }
805
826
  const TYPE_SPECS = [
806
- { kind: "special-publication", seriesCode: "SP" },
827
+ {
828
+ kind: "special-publication", seriesCode: "SP",
829
+ // The default-series plain-number branch (pubid#711): a citation
830
+ // shaped like the docnumber ("800-90B", publisher-led
831
+ // "NIST 800-90B") names an SP document — the docid-derived
832
+ // citation of NIST.SP.800-90B is exactly "800-90B" — and a bare
833
+ // plain-number parse carries the series' NIST publisher (the #704
834
+ // shape), so it is == to the spelled "NIST SP 800-90B" form a
835
+ // lookup compares against.
836
+ defaultSeries: "SP", seriesDefaultPublisher: "NIST",
837
+ },
807
838
  { kind: "federal-information-processing-standards", seriesCode: "FIPS", defaultPublisher: "", style: "fips" },
808
839
  { kind: "interagency-report", seriesCode: "IR", forceShort: true },
809
840
  { kind: "handbook", seriesCode: "HB" },
@@ -137,6 +137,17 @@ export function preprocessNist(input) {
137
137
  cleaned = cleaned.replace(/^(NBS|NIST)(IR|FIPS|GCR|HB|MONO|MP|NCSTAR|NSRDS)/i, "$1 $2");
138
138
  cleaned = cleaned.replace(/\b(ir|sp|tn|hb|fips|ams|vts)\b/i, (m) => m.toUpperCase());
139
139
  cleaned = cleaned.replace(/\bLC\b(?!IRC)/g, "LCIRC");
140
+ // normalize_fips_pub_alias: the FIPS series' official printed face is
141
+ // "FIPS PUB <number>" — FIPS is government-published and "PUB" (Federal
142
+ // Information Processing Standards PUBlication) is part of the official
143
+ // designation — and the RENDERER emits that face directly (owner ruling
144
+ // 2026-11-12, mirroring the gem). The grammar reads the
145
+ // publisher-bearing spelling only through this fold (pubid#697): the
146
+ // printed spellings — "FIPS PUB 205" and the publisher-prefixed
147
+ // "NIST FIPS PUB 205" (pubid#702) — fold onto the one model "NIST FIPS
148
+ // <number>" the grammar already builds (same hash, same URN, same MR
149
+ // string). Anchored at the head; bare "FIPS" is untouched.
150
+ cleaned = cleaned.replace(/^(?:NIST )?FIPS PUB\b/i, "NIST FIPS");
140
151
  // normalize_lcirc_supplement_contexts
141
152
  cleaned = cleaned.replace(/\bNBS LCIRC\b(?=.*\b(?:supp?|sup\+|r\d+\/)\d)/, "NBS.LCIRC");
142
153
  cleaned = cleaned.replace(/\bNBS\.LCIRC\.(\d+r\d+\/\d{4})/, "NBS LCIRC $1");
@@ -20,11 +20,17 @@ function buildRules() {
20
20
  rule("edition_part", () => str("第").then(digits.then(str("."), digits).as("edition"), str("版")));
21
21
  rule("edition_latin", () => digits.then(str("."), digits).as("edition"));
22
22
  rule("edition", () => space.then(rules["edition_part"].or(rules["edition_latin"])));
23
+ // MLIT's volume label after the edition: 本編 (main volume) or
24
+ // 本編+付録 (main volume with appendix). ASCII or full-width (U+3000)
25
+ // space; ASCII "+" or full-width "+" (U+FF0B), which the builder
26
+ // normalizes to "+".
27
+ rule("volume_sep", () => str(" ").or(str(" ")));
28
+ rule("volume", () => rules["volume_sep"].then(str("本編").then(str("+").or(str("+")).then(str("付録")).maybe()).as("volume")));
23
29
  rule("annex_supplement", () => space.then(str("Annex")).then(space, match("[A-Z]").as("annex_letter")));
24
30
  rule("handbook", () => str("PLATEAU")
25
31
  .then(space, rules["doc_type"], space, rules["number"])
26
32
  .then(rules["annex"].maybe())
27
- .then(rules["edition"].maybe()));
33
+ .then(rules["edition"].then(rules["volume"].maybe()).maybe()));
28
34
  rule("identifier", () => rules["handbook"]
29
35
  .then(rules["annex_supplement"])
30
36
  .as("base")
@@ -42,14 +48,21 @@ function plateauClass(kind) {
42
48
  number: { type: "integer" },
43
49
  annex: { type: "integer" },
44
50
  edition: { type: "string" },
51
+ // The volume label MLIT prints after the edition: "本編" (the main
52
+ // volume alone) or "本編+付録" (main volume and appendix, one
53
+ // PDF). A nil volume means the publication has no volume label:
54
+ // "#00 第5.0版" does not subsume "#00 第5.0版 本編+付録".
55
+ volume: { type: "string" },
45
56
  });
46
- static mappings = keyValue({ wire: "number", to: "number" }, { wire: "edition", to: "edition" }, { wire: "annex", to: "annex" });
57
+ static mappings = keyValue({ wire: "number", to: "number" }, { wire: "edition", to: "edition" }, { wire: "annex", to: "annex" }, { wire: "volume", to: "volume" });
47
58
  render() {
48
59
  let result = `PLATEAU ${isHandbook ? "Handbook" : "Technical Report"} #${pad2(this.number)}`;
49
60
  if (this.annex !== undefined)
50
61
  result += `-${this.annex}`;
51
62
  if (isHandbook && this.edition !== undefined)
52
63
  result += ` 第${this.edition}版`;
64
+ if (this.volume !== undefined)
65
+ result += ` ${this.volume}`;
53
66
  return result;
54
67
  }
55
68
  }
@@ -91,6 +104,12 @@ export function plateauGrammarImplementation() {
91
104
  if (data["edition"] !== undefined && data["edition"] !== null && kind === "handbook") {
92
105
  attrs["edition"] = String(data["edition"]);
93
106
  }
107
+ // The grammar reads a volume label only after an edition, on the
108
+ // Handbook; the builder normalizes the ASCII "+" to the
109
+ // full-width "+" (MLIT prints full-width).
110
+ if (data["volume"] !== undefined && data["volume"] !== null && kind === "handbook") {
111
+ attrs["volume"] = String(data["volume"]).replaceAll("+", "+");
112
+ }
94
113
  return new KIND_CLASSES[kind](attrs);
95
114
  },
96
115
  };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The pubid-scheme URN schema of DOI_PFS — the transcription
3
+ * of lib/pubid/<flavor>/pubid_urn_schema.rb (pubid#656, completed by
4
+ * #670; design in docs/URN-SCHEMES.adoc). GENERATED by
5
+ * scripts/port-urn-schemas.rb from the ruby declaration: edit the ruby
6
+ * schema and regenerate, never this table by hand.
7
+ */
8
+ export declare const doiPfsSchema: import("../schema.js").FlavorSchema;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The pubid-scheme URN schema of DOI_PFS — the transcription
3
+ * of lib/pubid/<flavor>/pubid_urn_schema.rb (pubid#656, completed by
4
+ * #670; design in docs/URN-SCHEMES.adoc). GENERATED by
5
+ * scripts/port-urn-schemas.rb from the ruby declaration: edit the ruby
6
+ * schema and regenerate, never this table by hand.
7
+ */
8
+ import { define } from "../index.js";
9
+ export const doiPfsSchema = define("doi_pfs", (s) => {
10
+ s.atom("prefix", "prefix");
11
+ s.atom("suffix", "suffix");
12
+ s.type("pubid:doi_pfs:identifier", "id", { core: [] });
13
+ s.type("pubid:doi_pfs:resource", "resource", { core: [] });
14
+ });
@@ -15,6 +15,9 @@ export const ihoSchema = define("iho", (s) => {
15
15
  s.atom("part", "part");
16
16
  s.atom("part_edition", "parted");
17
17
  s.atom("part_year", "party");
18
+ s.atom("spec", "spec");
19
+ s.atom("spec_sub", "specsub");
20
+ s.atom("spec_variant", "specvar");
18
21
  s.atom("annex", "annex");
19
22
  s.atom("annex_edition", "anned");
20
23
  s.atom("annex_year", "annyear");
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The registry of all 44 flavor pubid-urn schema tables — the TS mirror
2
+ * The registry of all 45 flavor pubid-urn schema tables — the TS mirror
3
3
  * of lib/pubid.rb's autoloaded Pubid::<Flavor>::PubidUrnSchema chain.
4
4
  * Declared tables only: importing this module has no side effects; the
5
5
  * pubid-urn module builds its flavor lookup lazily from this array.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The registry of all 44 flavor pubid-urn schema tables — the TS mirror
2
+ * The registry of all 45 flavor pubid-urn schema tables — the TS mirror
3
3
  * of lib/pubid.rb's autoloaded Pubid::<Flavor>::PubidUrnSchema chain.
4
4
  * Declared tables only: importing this module has no side effects; the
5
5
  * pubid-urn module builds its flavor lookup lazily from this array.
@@ -19,6 +19,7 @@ import { cencenelecSchema } from "./cencenelec.js";
19
19
  import { cieSchema } from "./cie.js";
20
20
  import { csaSchema } from "./csa.js";
21
21
  import { doiSchema } from "./doi.js";
22
+ import { doiPfsSchema } from "./doi_pfs.js";
22
23
  import { eascSchema } from "./easc.js";
23
24
  import { ecmaSchema } from "./ecma.js";
24
25
  import { etsiSchema } from "./etsi.js";
@@ -65,6 +66,7 @@ export const PUBID_URN_SCHEMAS = [
65
66
  cieSchema,
66
67
  csaSchema,
67
68
  doiSchema,
69
+ doiPfsSchema,
68
70
  eascSchema,
69
71
  ecmaSchema,
70
72
  etsiSchema,
@@ -10,6 +10,7 @@ export const plateauSchema = define("plateau", (s) => {
10
10
  s.atom("publisher", "pub");
11
11
  s.atom("edition", "ed");
12
12
  s.atom("annex", "annex", "int");
13
+ s.atom("volume", "vol");
13
14
  s.type("pubid:plateau:annex", "annex", {
14
15
  core: [],
15
16
  atoms: [["letter", "letter"]],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pubid/pubid",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
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",
@@ -0,0 +1 @@
1
+ {"bindings":null,"version":"0.1.0","grammar":"PubidCalconnectPfs","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Str":{"pattern":"CC"}},{"Str":{"pattern":"/"}},{"Entity":{"atom":8}},{"Entity":{"atom":6}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Alternative":{"atoms":[4,5]}},{"Repetition":{"atom":3,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"series","atom":7}},{"Sequence":{"atoms":[1,2]}},{"Repetition":{"atom":9,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" "}},{"Entity":{"atom":24}},{"Entity":{"atom":14}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":13,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Str":{"pattern":"-"}},{"Alternative":{"atoms":[16,17]}},{"Entity":{"atom":14}},{"Repetition":{"atom":19,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[18,20]}},{"Repetition":{"atom":21,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[15,22]}},{"Named":{"name":"number","atom":23}},{"Str":{"pattern":":"}},{"Entity":{"atom":29}},{"Entity":{"atom":14}},{"Repetition":{"atom":27,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"year","atom":28}},{"Str":{"pattern":"-"}},{"Entity":{"atom":34}},{"Entity":{"atom":14}},{"Repetition":{"atom":32,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"month","atom":33}},{"Str":{"pattern":"-"}},{"Entity":{"atom":39}},{"Entity":{"atom":14}},{"Repetition":{"atom":37,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"day","atom":38}},{"Sequence":{"atoms":[30,31,35,36]}},{"Repetition":{"atom":40,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[25,26,41]}},{"Repetition":{"atom":42,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[0,10,11,12,43]}}],"root":44},"bindings":[{"capture":"series","path":"series","type":"string","card":null,"preprocess":null},{"capture":"number","path":"number","type":"string","card":null,"preprocess":null},{"capture":"year","path":"year","type":"string","card":null,"preprocess":null},{"capture":"month","path":"month","type":"string","card":null,"preprocess":null},{"capture":"day","path":"day","type":"string","card":null,"preprocess":null}]}},"preprocess":{},"tables":{},"lint":{"order_warnings":[]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"CC 18011","expect":{}},{"entry":"identifier","kind":"accept","input":"CC 18011:2018","expect":{}},{"entry":"identifier","kind":"accept","input":"CC 11001:2024","expect":{}},{"entry":"identifier","kind":"accept","input":"CC/DIR 10006:2019","expect":{}},{"entry":"identifier","kind":"accept","input":"CC/Adv 0707.1:2007","expect":{}},{"entry":"identifier","kind":"accept","input":"CC/A 0812-1:2008","expect":{}},{"entry":"identifier","kind":"accept","input":"CC/WD 51017:2024-07-23","expect":{}},{"entry":"identifier","kind":"reject","input":"nonsense","expect":{}},{"entry":"identifier","kind":"reject","input":"CC","expect":{}},{"entry":"identifier","kind":"reject","input":"CC/WD","expect":{}},{"entry":"identifier","kind":"reject","input":"CC 18011:","expect":{}},{"entry":"identifier","kind":"reject","input":"CC 18011:2024-","expect":{}},{"entry":"identifier","kind":"example","input":"CC 18011","expect":{"number":"18011"}},{"entry":"identifier","kind":"example","input":"CC 18011:2018","expect":{"number":"18011","year":"2018"}},{"entry":"identifier","kind":"example","input":"CC 11001:2024","expect":{"number":"11001","year":"2024"}},{"entry":"identifier","kind":"example","input":"CC/DIR 10006:2019","expect":{"series":"DIR","number":"10006","year":"2019"}},{"entry":"identifier","kind":"example","input":"CC/Adv 0707.1:2007","expect":{"series":"Adv","number":"0707.1","year":"2007"}},{"entry":"identifier","kind":"example","input":"CC/A 0812-1:2008","expect":{"series":"A","number":"0812-1","year":"2008"}},{"entry":"identifier","kind":"example","input":"CC/WD 51017:2024-07-23","expect":{"series":"WD","number":"51017","year":"2024","month":"07","day":"23"}}],"docs":{},"source":"# PFS-generated grammar — DO NOT EDIT BY HAND.\n# Source: definitions/calconnect.pfs (pfs:1) sha256:2e864f0185a1a2f18a392f2f401ad3c9d65d1f32a9ec3f3f04f7dfca9b2226c4\n# Compiler: pubid/grammar/pfs 0.1.0\n# Flavor key: calconnect_pfs (grammar PubidCalconnectPfs)\n# Regenerate: ruby scripts/pfs compile definitions/calconnect.pfs --as calconnect_pfs\n\ngrammar PubidCalconnectPfs version \"0.1.0\" edition 2 {\n\n # Grammar fragments: named lexical terms declared under `grammar:`.\n digit = %x30-39\n letter = %x41-5A / %x61-7A\n space = \" \"\n slash = \"/\"\n colon = \":\"\n dash = \"-\"\n\n # Fields: one capture rule per declared field; the whole pattern\n # is the capture.\n calconnect_series = (1*letter) as series\n calconnect_number = (1*digit [(\".\" / \"-\") 1*digit]) as number\n calconnect_year = (1*digit) as year\n calconnect_month = (1*digit) as month\n calconnect_day = (1*digit) as day\n\n # Identifier rule: the canonical template first, then the\n # declared alternates (parse-in spellings; they normalize to the\n # canonical render face).\n identifier = \"CC\" [\"/\" calconnect_series] \" \" calconnect_number [\":\" calconnect_year [\"-\" calconnect_month \"-\" calconnect_day]]\n entry identifier: identifier\n\n # Typed model bindings: every bound capture lands on the\n # consumer model's field of the same name.\n bindings identifier {\n series -> series (string)\n number -> number (string)\n year -> year (string)\n month -> month (string)\n day -> day (string)\n }\n\n # REQUIRED examples from the definition — accepts, rejects, and\n # the capture expectations the compiler replayed at compile time.\n test {\n accept \"CC 18011\"\n accept \"CC 18011:2018\"\n accept \"CC 11001:2024\"\n accept \"CC/DIR 10006:2019\"\n accept \"CC/Adv 0707.1:2007\"\n accept \"CC/A 0812-1:2008\"\n accept \"CC/WD 51017:2024-07-23\"\n reject \"nonsense\"\n reject \"CC\"\n reject \"CC/WD\"\n reject \"CC 18011:\"\n reject \"CC 18011:2024-\"\n example \"CC 18011\" { number: \"18011\" }\n example \"CC 18011:2018\" { number: \"18011\" year: \"2018\" }\n example \"CC 11001:2024\" { number: \"11001\" year: \"2024\" }\n example \"CC/DIR 10006:2019\" { series: \"DIR\" number: \"10006\" year: \"2019\" }\n example \"CC/Adv 0707.1:2007\" { series: \"Adv\" number: \"0707.1\" year: \"2007\" }\n example \"CC/A 0812-1:2008\" { series: \"A\" number: \"0812-1\" year: \"2008\" }\n example \"CC/WD 51017:2024-07-23\" { series: \"WD\" number: \"51017\" year: \"2024\" month: \"07\" day: \"23\" }\n }\n}\n","source_hash":"35a33d659c86202186a5e6beb8c7df58af7bd6667dd55b6cb3cecb8855713b85","unicode_tables":"onigmo (Ruby 3.4.8)","compiler_version":"1.3.126","edition":2,"semantics_hash":"sha256:9fbceffd1828c207a9ec854699d0801fad9e52521a972e39dc1838fb80440dc8","checksum":"sha256:6557fd4360e2deab7ca4062b72e5cd6e60b6e3cd0252cb5b1e30d66f3905f467"}
@@ -0,0 +1 @@
1
+ {"bindings":null,"version":"0.1.0","grammar":"PubidCenPfs","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Str":{"pattern":"EN"}},{"Str":{"pattern":" "}},{"Entity":{"atom":8}},{"Re":{"pattern":"[\\x31-\\x39]"}},{"Entity":{"atom":5}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":4,"min":0,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[3,6]}},{"Named":{"name":"number","atom":7}},{"Str":{"pattern":"-"}},{"Entity":{"atom":13}},{"Entity":{"atom":5}},{"Repetition":{"atom":11,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"part","atom":12}},{"Sequence":{"atoms":[9,10]}},{"Repetition":{"atom":14,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":":"}},{"Entity":{"atom":20}},{"Entity":{"atom":5}},{"Repetition":{"atom":18,"min":4,"max":null,"tag":"Repetition"}},{"Named":{"name":"year","atom":19}},{"Sequence":{"atoms":[16,17]}},{"Repetition":{"atom":21,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[0,1,2,15,22]}}],"root":23},"bindings":[{"capture":"number","path":"number","type":"string","card":null,"preprocess":null},{"capture":"part","path":"part","type":"string","card":null,"preprocess":null},{"capture":"year","path":"year","type":"string","card":null,"preprocess":null}]}},"preprocess":{},"tables":{},"lint":{"order_warnings":[]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"EN 1325","expect":{}},{"entry":"identifier","kind":"accept","input":"EN 12464-1:2011","expect":{}},{"entry":"identifier","kind":"accept","input":"EN 1177:2018","expect":{}},{"entry":"identifier","kind":"reject","input":"EN","expect":{}},{"entry":"identifier","kind":"reject","input":"EN ISO 9001:2015","expect":{}},{"entry":"identifier","kind":"reject","input":"nonsense","expect":{}},{"entry":"identifier","kind":"reject","input":"CEN/TS 12345","expect":{}},{"entry":"identifier","kind":"example","input":"EN 1325","expect":{"number":"1325"}},{"entry":"identifier","kind":"example","input":"EN 12464-1:2011","expect":{"number":"12464","part":"1","year":"2011"}},{"entry":"identifier","kind":"example","input":"EN 1177:2018","expect":{"number":"1177","year":"2018"}}],"docs":{},"source":"# PFS-generated grammar — DO NOT EDIT BY HAND.\n# Source: definitions/cen.pfs (pfs:1) sha256:bc1edd5874f8938b5f946416a2b052d9d9b9df3d99f6c20a3d88fcab9ae1d70c\n# Compiler: pubid/grammar/pfs 0.1.0\n# Flavor key: cen_pfs (grammar PubidCenPfs)\n# Regenerate: ruby scripts/pfs compile definitions/cen.pfs --as cen_pfs\n# Module: iso.inner v1 (sha256:6f04cf475f1c...)\n# Module: en.overlay v1 (sha256:851af8c5f31b...)\n# Module: national.prefix v1 (sha256:c7aa2f2442e8...)\n# Module: supplements.core v1 (sha256:9fa9656375c7...)\n\ngrammar PubidCenPfs version \"0.1.0\" edition 2 {\n\n # Grammar fragments: named lexical terms declared under `grammar:`.\n iso_space = \" \"\n iso_colon = \":\"\n iso_dash = \"-\"\n iso_year = 4*digit\n iso_part = 1*digit [\"-\" 1*digit]\n iso_number = 1*(\"0\" %x31-39) *digit [iso_dash iso_part]\n iso_document = \"ISO\" iso_space iso_number [iso_colon iso_year]\n en_space = \" \"\n en_draft = (\"pr\" / \"Fpr\")\n en_face = [en_draft] \"EN\"\n en_adoption = en_face en_space iso_document\n supplement_number = 1*digit\n supplement_year = 4*digit\n supplement_month = 2*digit\n digit = %x30-39\n space = \" \"\n slash = \"/\"\n colon = \":\"\n dash = \"-\"\n\n # Fields: one capture rule per declared field; the whole pattern\n # is the capture.\n cen_cenelec_number = ((%x31-39) *digit) as number\n cen_cenelec_part = (1*digit) as part\n cen_cenelec_year = (4*digit) as year\n\n # Identifier rule: the canonical template first, then the\n # declared alternates (parse-in spellings; they normalize to the\n # canonical render face).\n identifier = \"EN\" \" \" cen_cenelec_number [\"-\" cen_cenelec_part] [\":\" cen_cenelec_year]\n entry identifier: identifier\n\n # Typed model bindings: every bound capture lands on the\n # consumer model's field of the same name.\n bindings identifier {\n number -> number (string)\n part -> part (string)\n year -> year (string)\n }\n\n # REQUIRED examples from the definition — accepts, rejects, and\n # the capture expectations the compiler replayed at compile time.\n test {\n accept \"EN 1325\"\n accept \"EN 12464-1:2011\"\n accept \"EN 1177:2018\"\n reject \"EN\"\n reject \"EN ISO 9001:2015\"\n reject \"nonsense\"\n reject \"CEN/TS 12345\"\n example \"EN 1325\" { number: \"1325\" }\n example \"EN 12464-1:2011\" { number: \"12464\" part: \"1\" year: \"2011\" }\n example \"EN 1177:2018\" { number: \"1177\" year: \"2018\" }\n }\n}\n","source_hash":"4b84b6acda6f62fbbff60e0ff603d72e3ff00b71906914855633b6f0404869db","unicode_tables":"onigmo (Ruby 3.4.8)","compiler_version":"1.3.126","edition":2,"semantics_hash":"sha256:4363f170082d3242a35648e8b019037e9670c804d4ea951a51487697eb0c4cee","checksum":"sha256:e19eae585ed8ee560c88d97c8289d827384d21214db02634bc3eeff5a0296ac9"}
@@ -0,0 +1 @@
1
+ {"bindings":null,"version":"0.1.0","grammar":"PubidDoiPfs","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Str":{"pattern":"doi:"}},{"Entity":{"atom":8}},{"Str":{"pattern":"10"}},{"Str":{"pattern":"."}},{"Entity":{"atom":5}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":4,"min":2,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[2,3,6]}},{"Named":{"name":"prefix","atom":7}},{"Str":{"pattern":"/"}},{"Entity":{"atom":23}},{"Entity":{"atom":21}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[.]"}},{"Re":{"pattern":"[_]"}},{"Re":{"pattern":"[\\-]"}},{"Re":{"pattern":"[/]"}},{"Re":{"pattern":"[(]"}},{"Re":{"pattern":"[)]"}},{"Alternative":{"atoms":[12,13,14,15,16,17,18,19,20]}},{"Repetition":{"atom":11,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"suffix","atom":22}},{"Sequence":{"atoms":[0,1,9,10]}},{"Entity":{"atom":33}},{"Str":{"pattern":"https://"}},{"Str":{"pattern":"http://"}},{"Alternative":{"atoms":[26,27]}},{"Repetition":{"atom":28,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":"doi.org"}},{"Repetition":{"atom":30,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[29,31]}},{"Repetition":{"atom":32,"min":0,"max":1,"tag":"Maybe"}},{"Named":{"name":"url_prefix","atom":25}},{"Repetition":{"atom":34,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":41}},{"Str":{"pattern":"doi:"}},{"Str":{"pattern":"DOI:"}},{"Str":{"pattern":"Doi:"}},{"Alternative":{"atoms":[37,38,39]}},{"Repetition":{"atom":40,"min":0,"max":1,"tag":"Maybe"}},{"Named":{"name":"scheme_prefix","atom":36}},{"Repetition":{"atom":42,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":45}},{"Str":{"pattern":"/"}},{"Named":{"name":"slash","atom":44}},{"Repetition":{"atom":46,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":8}},{"Str":{"pattern":"/"}},{"Entity":{"atom":23}},{"Sequence":{"atoms":[35,43,47,48,49,50]}},{"Alternative":{"atoms":[24,51]}}],"root":52},"bindings":[{"capture":"prefix","path":"prefix","type":"string","card":null,"preprocess":null},{"capture":"suffix","path":"suffix","type":"string","card":null,"preprocess":null}]}},"preprocess":{},"tables":{},"lint":{"order_warnings":["rule identifier: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)"]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"doi:10.1000/182","expect":{}},{"entry":"identifier","kind":"accept","input":"10.1006/jmbi.1998.2354","expect":{}},{"entry":"identifier","kind":"accept","input":"DOI:10.6028/NIST.2022-04-15.001","expect":{}},{"entry":"identifier","kind":"accept","input":"https://doi.org/10.1000/182","expect":{}},{"entry":"identifier","kind":"reject","input":"doi:","expect":{}},{"entry":"identifier","kind":"reject","input":"doi:10","expect":{}},{"entry":"identifier","kind":"reject","input":"doi:10.X/abc","expect":{}},{"entry":"identifier","kind":"reject","input":"doi:10.1000/","expect":{}},{"entry":"identifier","kind":"reject","input":"nonsense","expect":{}},{"entry":"identifier","kind":"example","input":"doi:10.1000/182","expect":{"prefix":"10.1000","suffix":"182"}},{"entry":"identifier","kind":"example","input":"10.1006/jmbi.1998.2354","expect":{"prefix":"10.1006","suffix":"jmbi.1998.2354"}},{"entry":"identifier","kind":"example","input":"DOI:10.6028/NIST.2022-04-15.001","expect":{"prefix":"10.6028","suffix":"NIST.2022-04-15.001"}},{"entry":"identifier","kind":"example","input":"https://doi.org/10.1000/182","expect":{"prefix":"10.1000","suffix":"182"}}],"docs":{},"source":"# PFS-generated grammar — DO NOT EDIT BY HAND.\n# Source: definitions/doi.pfs (pfs:1) sha256:60abc3c403c61019d727ee44d9a72591c1549672451e6609a72d36189cae6340\n# Compiler: pubid/grammar/pfs 0.1.0\n# Flavor key: doi_pfs (grammar PubidDoiPfs)\n# Regenerate: ruby scripts/pfs compile definitions/doi.pfs --as doi_pfs\n\ngrammar PubidDoiPfs version \"0.1.0\" edition 2 {\n\n # Grammar fragments: named lexical terms declared under `grammar:`.\n digit = %x30-39\n suffix_char = %x30-39 / %x41-5A / %x61-7A / %x2E / %x5F / %x2D / %x2F / %x28 / %x29\n slash = \"/\"\n url_prefix = [[\"https://\" / \"http://\"] [\"doi.org\"]]\n scheme_prefix = [\"doi:\" / \"DOI:\" / \"Doi:\"]\n\n # Fields: one capture rule per declared field; the whole pattern\n # is the capture.\n doi_prefix = (\"10\" \".\" 2*digit) as prefix\n doi_suffix = (1*suffix_char) as suffix\n\n # Identifier rule: the canonical template first, then the\n # declared alternates (parse-in spellings; they normalize to the\n # canonical render face).\n identifier = {\n \"doi:\" doi_prefix \"/\" doi_suffix\n / [url_prefix] [scheme_prefix] [slash] doi_prefix \"/\" doi_suffix\n }\n entry identifier: identifier\n\n # Typed model bindings: every bound capture lands on the\n # consumer model's field of the same name.\n bindings identifier {\n prefix -> prefix (string)\n suffix -> suffix (string)\n }\n\n # REQUIRED examples from the definition — accepts, rejects, and\n # the capture expectations the compiler replayed at compile time.\n test {\n accept \"doi:10.1000/182\"\n accept \"10.1006/jmbi.1998.2354\"\n accept \"DOI:10.6028/NIST.2022-04-15.001\"\n accept \"https://doi.org/10.1000/182\"\n reject \"doi:\"\n reject \"doi:10\"\n reject \"doi:10.X/abc\"\n reject \"doi:10.1000/\"\n reject \"nonsense\"\n example \"doi:10.1000/182\" { prefix: \"10.1000\" suffix: \"182\" }\n example \"10.1006/jmbi.1998.2354\" { prefix: \"10.1006\" suffix: \"jmbi.1998.2354\" }\n example \"DOI:10.6028/NIST.2022-04-15.001\" { prefix: \"10.6028\" suffix: \"NIST.2022-04-15.001\" }\n example \"https://doi.org/10.1000/182\" { prefix: \"10.1000\" suffix: \"182\" }\n }\n}\n","source_hash":"c872de21dfa95b1add4eed4785182c6e661fc79a0a8c15c07eb6209621a20f2c","unicode_tables":"onigmo (Ruby 3.4.8)","compiler_version":"1.3.126","edition":2,"semantics_hash":"sha256:06d0039460d751282b122dd5834bd505046c2cb32605c051ac296f2c29f964b3","checksum":"sha256:25f3643ce0fe181ae16b91a87a43177d495079ed590c6eee3c3cf36825e1fc05"}
@@ -0,0 +1 @@
1
+ {"bindings":null,"version":"0.1.0","grammar":"PubidEtsiPfs","shape":"parsanol-tree/v2","binding_version":1,"entries":{"identifier":{"root":"identifier","grammar":{"atoms":[{"Str":{"pattern":"ETSI "}},{"Entity":{"atom":18}},{"Str":{"pattern":"I-ETS"}},{"Str":{"pattern":"TCRTR"}},{"Str":{"pattern":"GTS"}},{"Str":{"pattern":"ETR"}},{"Str":{"pattern":"ETS"}},{"Str":{"pattern":"TBR"}},{"Str":{"pattern":"NET"}},{"Str":{"pattern":"EN"}},{"Str":{"pattern":"ES"}},{"Str":{"pattern":"EG"}},{"Str":{"pattern":"TS"}},{"Str":{"pattern":"GR"}},{"Str":{"pattern":"GS"}},{"Str":{"pattern":"SR"}},{"Str":{"pattern":"TR"}},{"Alternative":{"atoms":[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}},{"Named":{"name":"type","atom":17}},{"Str":{"pattern":" "}},{"Entity":{"atom":54}},{"Str":{"pattern":"GSM"}},{"Str":{"pattern":" "}},{"Entity":{"atom":24}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Repetition":{"atom":23,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Entity":{"atom":24}},{"Repetition":{"atom":27,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[21,22,25,26,28]}},{"Entity":{"atom":24}},{"Repetition":{"atom":30,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"."}},{"Entity":{"atom":24}},{"Repetition":{"atom":33,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[31,32,34]}},{"Entity":{"atom":40}},{"Re":{"pattern":"[\\x41-\\x5a]"}},{"Re":{"pattern":"[\\x61-\\x7a]"}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Alternative":{"atoms":[37,38,39]}},{"Repetition":{"atom":36,"min":1,"max":null,"tag":"Repetition"}},{"Str":{"pattern":"-"}},{"Entity":{"atom":40}},{"Repetition":{"atom":43,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[42,44]}},{"Repetition":{"atom":45,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" "}},{"Entity":{"atom":24}},{"Repetition":{"atom":48,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[41,46,47,49]}},{"Entity":{"atom":24}},{"Repetition":{"atom":51,"min":1,"max":null,"tag":"Repetition"}},{"Alternative":{"atoms":[29,35,50,52]}},{"Named":{"name":"number","atom":53}},{"Sequence":{"atoms":[0,1,19,20]}},{"Str":{"pattern":"ETSI "}},{"Entity":{"atom":18}},{"Str":{"pattern":" "}},{"Entity":{"atom":54}},{"Str":{"pattern":" "}},{"Entity":{"atom":64}},{"Entity":{"atom":24}},{"Repetition":{"atom":62,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"minor","atom":63}},{"Sequence":{"atoms":[60,61]}},{"Repetition":{"atom":65,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":73}},{"Str":{"pattern":"-"}},{"Entity":{"atom":40}},{"Repetition":{"atom":69,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[68,70]}},{"Repetition":{"atom":71,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"parts","atom":72}},{"Repetition":{"atom":67,"min":0,"max":1,"tag":"Maybe"}},{"Entity":{"atom":83}},{"Str":{"pattern":"/A"}},{"Str":{"pattern":"/C"}},{"Alternative":{"atoms":[76,77]}},{"Entity":{"atom":24}},{"Repetition":{"atom":79,"min":1,"max":null,"tag":"Repetition"}},{"Sequence":{"atoms":[78,80]}},{"Repetition":{"atom":81,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"supplements","atom":82}},{"Repetition":{"atom":75,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" V"}},{"Entity":{"atom":92}},{"Entity":{"atom":90}},{"Re":{"pattern":"[\\x30-\\x39]"}},{"Re":{"pattern":"[.]"}},{"Alternative":{"atoms":[88,89]}},{"Repetition":{"atom":87,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"version","atom":91}},{"Sequence":{"atoms":[85,86]}},{"Str":{"pattern":" ed."}},{"Entity":{"atom":98}},{"Entity":{"atom":24}},{"Repetition":{"atom":96,"min":1,"max":null,"tag":"Repetition"}},{"Named":{"name":"edition","atom":97}},{"Sequence":{"atoms":[94,95]}},{"Alternative":{"atoms":[93,99]}},{"Repetition":{"atom":100,"min":0,"max":1,"tag":"Maybe"}},{"Str":{"pattern":" ("}},{"Entity":{"atom":106}},{"Entity":{"atom":24}},{"Repetition":{"atom":104,"min":4,"max":4,"tag":"Repetition"}},{"Named":{"name":"year","atom":105}},{"Str":{"pattern":"-"}},{"Entity":{"atom":111}},{"Entity":{"atom":24}},{"Repetition":{"atom":109,"min":2,"max":2,"tag":"Repetition"}},{"Named":{"name":"month","atom":110}},{"Str":{"pattern":")"}},{"Sequence":{"atoms":[102,103,107,108,112]}},{"Repetition":{"atom":113,"min":0,"max":1,"tag":"Maybe"}},{"Sequence":{"atoms":[56,57,58,59,66,74,84,101,114]}},{"Alternative":{"atoms":[55,115]}}],"root":116},"bindings":[{"capture":"type","path":"type","type":"string","card":null,"preprocess":null},{"capture":"number","path":"number","type":"string","card":null,"preprocess":null},{"capture":"minor","path":"minor","type":"string","card":null,"preprocess":null},{"capture":"parts","path":"parts","type":"string","card":null,"preprocess":null},{"capture":"supplements","path":"supplements","type":"string","card":null,"preprocess":null},{"capture":"version","path":"version","type":"string","card":null,"preprocess":null},{"capture":"edition","path":"edition","type":"string","card":null,"preprocess":null},{"capture":"year","path":"year","type":"string","card":null,"preprocess":null},{"capture":"month","path":"month","type":"string","card":null,"preprocess":null}]}},"preprocess":{},"tables":{},"lint":{"order_warnings":["rule identifier: branches 1 and 2 are order-dependent (shared first bytes; ordered choice is decisive)"]},"default_entry":"identifier","render":{},"derive":{},"tests":[{"entry":"identifier","kind":"accept","input":"ETSI EG 200 053 V1.5.1 (2004-06)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EN 300 175","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EN 300 175-2","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EN 300 019-1-0 V2.1.2 (2003-09)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI TS 103 702","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI EG 201 880 V1.2.1","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI GTS GSM 02.01 V5.5.0 (1999-08)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI I-ETS 300 003 ed.1 (1991-08)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI ETR 108/A1 ed.1 (1995-08)","expect":{}},{"entry":"identifier","kind":"accept","input":"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)","expect":{}},{"entry":"identifier","kind":"reject","input":"nonsense","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI XX 100","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 V","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 V1.2.x","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 (","expect":{}},{"entry":"identifier","kind":"reject","input":"ETSI EN 300 175 (2004-6)","expect":{}},{"entry":"identifier","kind":"example","input":"ETSI EG 200 053 V1.5.1 (2004-06)","expect":{"type":"EG","number":"200 053","version":"1.5.1","year":"2004","month":"06"}},{"entry":"identifier","kind":"example","input":"ETSI EN 300 175","expect":{"type":"EN","number":"300 175"}},{"entry":"identifier","kind":"example","input":"ETSI EN 300 175-2","expect":{"type":"EN","number":"300 175","parts":"-2"}},{"entry":"identifier","kind":"example","input":"ETSI EN 300 019-1-0 V2.1.2 (2003-09)","expect":{"type":"EN","number":"300 019","parts":"-1-0","version":"2.1.2","year":"2003","month":"09"}},{"entry":"identifier","kind":"example","input":"ETSI TS 103 702","expect":{"type":"TS","number":"103 702"}},{"entry":"identifier","kind":"example","input":"ETSI EG 201 880 V1.2.1","expect":{"type":"EG","number":"201 880","version":"1.2.1"}},{"entry":"identifier","kind":"example","input":"ETSI GTS GSM 02.01 V5.5.0 (1999-08)","expect":{"type":"GTS","number":"GSM 02.01","version":"5.5.0","year":"1999","month":"08"}},{"entry":"identifier","kind":"example","input":"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)","expect":{"type":"GR","number":"NFV-EVE 008","version":"3.1.1","year":"2017","month":"12"}},{"entry":"identifier","kind":"example","input":"ETSI I-ETS 300 003 ed.1 (1991-08)","expect":{"type":"I-ETS","number":"300 003","edition":"1","year":"1991","month":"08"}},{"entry":"identifier","kind":"example","input":"ETSI ETR 108/A1 ed.1 (1995-08)","expect":{"type":"ETR","number":"108","supplements":"/A1","edition":"1","year":"1995","month":"08"}},{"entry":"identifier","kind":"example","input":"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)","expect":{"type":"ETS","number":"300 092","parts":"-1","supplements":"/A1/C1","edition":"1","year":"1994","month":"04"}}],"docs":{},"source":"# PFS-generated grammar — DO NOT EDIT BY HAND.\n# Source: definitions/etsi.pfs (pfs:1) sha256:9343088bf087b55ea790057e6be0bbebe2272407ee81f035b52e5f9a4d5c7b76\n# Compiler: pubid/grammar/pfs 0.1.0\n# Flavor key: etsi_pfs (grammar PubidEtsiPfs)\n# Regenerate: ruby scripts/pfs compile definitions/etsi.pfs --as etsi_pfs\n\ngrammar PubidEtsiPfs version \"0.1.0\" edition 2 {\n\n # Grammar fragments: named lexical terms declared under `grammar:`.\n digit = %x30-39\n alnum_char = %x41-5A / %x61-7A / %x30-39\n version_char = %x30-39 / %x2E\n\n # Fields: one capture rule per declared field; the whole pattern\n # is the capture.\n etsi_type = (\"I-ETS\" / \"TCRTR\" / \"GTS\" / \"ETR\" / \"ETS\" / \"TBR\" / \"NET\" / \"EN\" / \"ES\" / \"EG\" / \"TS\" / \"GR\" / \"GS\" / \"SR\" / \"TR\") as type\n etsi_number = ((\"GSM\" \" \" 1*digit \".\" 1*digit) / (1*digit \".\" 1*digit) / (1*alnum_char [(\"-\" 1*alnum_char)] \" \" 1*digit) / (1*digit)) as number\n etsi_minor = (1*digit) as minor\n etsi_parts = (1*(\"-\" 1*alnum_char)) as parts\n etsi_supplements = (1*((\"/A\" / \"/C\") 1*digit)) as supplements\n etsi_version = (1*version_char) as version\n etsi_edition = (1*digit) as edition\n etsi_year = (4digit) as year\n etsi_month = (2digit) as month\n\n # Identifier rule: the canonical template first, then the\n # declared alternates (parse-in spellings; they normalize to the\n # canonical render face).\n identifier = {\n \"ETSI \" etsi_type \" \" etsi_number\n / \"ETSI \" etsi_type \" \" etsi_number [\" \" etsi_minor] [etsi_parts] [etsi_supplements] [( \" V\" etsi_version / \" ed.\" etsi_edition )] [\" (\" etsi_year \"-\" etsi_month \")\"]\n }\n entry identifier: identifier\n\n # Typed model bindings: every bound capture lands on the\n # consumer model's field of the same name.\n bindings identifier {\n type -> type (string)\n number -> number (string)\n minor -> minor (string)\n parts -> parts (string)\n supplements -> supplements (string)\n version -> version (string)\n edition -> edition (string)\n year -> year (string)\n month -> month (string)\n }\n\n # REQUIRED examples from the definition — accepts, rejects, and\n # the capture expectations the compiler replayed at compile time.\n test {\n accept \"ETSI EG 200 053 V1.5.1 (2004-06)\"\n accept \"ETSI EN 300 175\"\n accept \"ETSI EN 300 175-2\"\n accept \"ETSI EN 300 019-1-0 V2.1.2 (2003-09)\"\n accept \"ETSI TS 103 702\"\n accept \"ETSI EG 201 880 V1.2.1\"\n accept \"ETSI GTS GSM 02.01 V5.5.0 (1999-08)\"\n accept \"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)\"\n accept \"ETSI I-ETS 300 003 ed.1 (1991-08)\"\n accept \"ETSI ETR 108/A1 ed.1 (1995-08)\"\n accept \"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)\"\n reject \"nonsense\"\n reject \"ETSI\"\n reject \"ETSI EN\"\n reject \"ETSI XX 100\"\n reject \"ETSI EN 300 175 V\"\n reject \"ETSI EN 300 175 V1.2.x\"\n reject \"ETSI EN 300 175 (\"\n reject \"ETSI EN 300 175 (2004-6)\"\n example \"ETSI EG 200 053 V1.5.1 (2004-06)\" { type: \"EG\" number: \"200 053\" version: \"1.5.1\" year: \"2004\" month: \"06\" }\n example \"ETSI EN 300 175\" { type: \"EN\" number: \"300 175\" }\n example \"ETSI EN 300 175-2\" { type: \"EN\" number: \"300 175\" parts: \"-2\" }\n example \"ETSI EN 300 019-1-0 V2.1.2 (2003-09)\" { type: \"EN\" number: \"300 019\" parts: \"-1-0\" version: \"2.1.2\" year: \"2003\" month: \"09\" }\n example \"ETSI TS 103 702\" { type: \"TS\" number: \"103 702\" }\n example \"ETSI EG 201 880 V1.2.1\" { type: \"EG\" number: \"201 880\" version: \"1.2.1\" }\n example \"ETSI GTS GSM 02.01 V5.5.0 (1999-08)\" { type: \"GTS\" number: \"GSM 02.01\" version: \"5.5.0\" year: \"1999\" month: \"08\" }\n example \"ETSI GR NFV-EVE 008 V3.1.1 (2017-12)\" { type: \"GR\" number: \"NFV-EVE 008\" version: \"3.1.1\" year: \"2017\" month: \"12\" }\n example \"ETSI I-ETS 300 003 ed.1 (1991-08)\" { type: \"I-ETS\" number: \"300 003\" edition: \"1\" year: \"1991\" month: \"08\" }\n example \"ETSI ETR 108/A1 ed.1 (1995-08)\" { type: \"ETR\" number: \"108\" supplements: \"/A1\" edition: \"1\" year: \"1995\" month: \"08\" }\n example \"ETSI ETS 300 092-1/A1/C1 ed.1 (1994-04)\" { type: \"ETS\" number: \"300 092\" parts: \"-1\" supplements: \"/A1/C1\" edition: \"1\" year: \"1994\" month: \"04\" }\n }\n}\n","source_hash":"f5e47bb403d9bff4737d8a200f08c71f8029df12f96c56e9774b722908bfa853","unicode_tables":"onigmo (Ruby 3.4.8)","compiler_version":"1.3.126","edition":2,"semantics_hash":"sha256:204a90b72aa2ea5fc2249622bd61c052b4ba5d1ebf732a2c2b57669c8de24393","checksum":"sha256:d861e040f36868dcef43c6547c5791979d4832d1ec1ca6bc310bdad82ea0e930"}