@pubid/pubid 0.1.37 → 0.1.38
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.
- package/artifact/corpus.json.gz +0 -0
- package/dist/flavors/ecma/index.js +35 -1
- package/dist/flavors/iec/grammar.js +39 -2
- package/dist/flavors/iec/index.js +110 -0
- package/dist/flavors/iec/model.js +29 -4
- package/dist/flavors/iho/index.js +126 -5
- package/dist/flavors/iso/grammar.d.ts +5 -0
- package/dist/flavors/iso/grammar.js +68 -1
- package/dist/flavors/iso/index.js +65 -1
- package/dist/flavors/iso/model.js +7 -1
- package/dist/flavors/iso/stages.generated.js +1 -1
- package/dist/flavors/nist/model.js +18 -2
- package/dist/flavors/nist/preprocessor.js +11 -0
- package/dist/flavors/plateau/index.js +21 -2
- package/dist/pubid-urn/schemas/iho.js +3 -0
- package/dist/pubid-urn/schemas/plateau.js +1 -0
- package/package.json +1 -1
- package/vendor/parg/iec.json +1 -1
- package/vendor/parg/iso.json +1 -1
|
@@ -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
|
|
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"]
|
|
@@ -709,8 +709,24 @@ function nistClass(spec) {
|
|
|
709
709
|
return `${this.publisher ?? "NIST"} ${this.date_year ?? ""}-${this.date_month ?? ""}-${this.date_day ?? ""} ${this.dated_seq ?? ""}`;
|
|
710
710
|
}
|
|
711
711
|
if (spec.style === "fips") {
|
|
712
|
-
|
|
713
|
-
|
|
712
|
+
// The official published face is "FIPS PUB <number>": FIPS is
|
|
713
|
+
// government-published and "PUB" (Federal Information Processing
|
|
714
|
+
// Standards PUBlication) is part of the official designation
|
|
715
|
+
// (owner ruling 2026-11-12). A spelled NIST publisher renders the
|
|
716
|
+
// official face; the historical NBS-prefixed rows keep
|
|
717
|
+
// "NBS FIPS <number>", and the bare publisher-less spelling keeps
|
|
718
|
+
// its short "FIPS <number>" face. The lead reads
|
|
719
|
+
// shortRenderPublisher() (the printed-publisher view), so a
|
|
720
|
+
// series-default publisher filled without being spelled (#704's
|
|
721
|
+
// ruby model) can never flip the bare face; the tail rides the
|
|
722
|
+
// lead.
|
|
723
|
+
const pub = this.shortRenderPublisher();
|
|
724
|
+
const series = this.seriesCode() ?? "";
|
|
725
|
+
let result = pub === "NIST"
|
|
726
|
+
? "FIPS PUB"
|
|
727
|
+
: pub !== undefined && pub !== ""
|
|
728
|
+
? `${pub} ${series}`
|
|
729
|
+
: series;
|
|
714
730
|
let numberPart = this.number ?? "";
|
|
715
731
|
const fy = /^(\d{1,3})-(\d{4})$/.exec(numberPart);
|
|
716
732
|
if (fy !== null) {
|
|
@@ -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
|
};
|
|
@@ -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");
|
|
@@ -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.
|
|
3
|
+
"version": "0.1.38",
|
|
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",
|