@pubid/pubid 0.1.33 → 0.1.35
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/api/grammar.js +9 -2
- package/dist/flavors/api/index.js +31 -2
- package/dist/flavors/bipm/index.js +89 -14
- package/dist/flavors/bsi/model.js +13 -9
- package/dist/flavors/iec/index.js +15 -4
- package/dist/flavors/iec/model.js +26 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/model/identifier.d.ts +3 -0
- package/dist/model/identifier.js +6 -0
- package/dist/pubid-urn/decoder.d.ts +27 -0
- package/dist/pubid-urn/decoder.js +165 -0
- package/dist/pubid-urn/encoder.d.ts +30 -0
- package/dist/pubid-urn/encoder.js +197 -0
- package/dist/pubid-urn/errors.d.ts +15 -0
- package/dist/pubid-urn/errors.js +21 -0
- package/dist/pubid-urn/escape.d.ts +19 -0
- package/dist/pubid-urn/escape.js +65 -0
- package/dist/pubid-urn/index.d.ts +65 -0
- package/dist/pubid-urn/index.js +216 -0
- package/dist/pubid-urn/paths.d.ts +26 -0
- package/dist/pubid-urn/paths.js +129 -0
- package/dist/pubid-urn/schema.d.ts +88 -0
- package/dist/pubid-urn/schema.js +186 -0
- package/dist/pubid-urn/schemas/adobe.d.ts +8 -0
- package/dist/pubid-urn/schemas/adobe.js +15 -0
- package/dist/pubid-urn/schemas/amca.d.ts +8 -0
- package/dist/pubid-urn/schemas/amca.js +19 -0
- package/dist/pubid-urn/schemas/ansi.d.ts +8 -0
- package/dist/pubid-urn/schemas/ansi.js +30 -0
- package/dist/pubid-urn/schemas/api.d.ts +8 -0
- package/dist/pubid-urn/schemas/api.js +25 -0
- package/dist/pubid-urn/schemas/ashrae.d.ts +8 -0
- package/dist/pubid-urn/schemas/ashrae.js +28 -0
- package/dist/pubid-urn/schemas/asme.d.ts +8 -0
- package/dist/pubid-urn/schemas/asme.js +26 -0
- package/dist/pubid-urn/schemas/astm.d.ts +8 -0
- package/dist/pubid-urn/schemas/astm.js +38 -0
- package/dist/pubid-urn/schemas/bipm.d.ts +8 -0
- package/dist/pubid-urn/schemas/bipm.js +34 -0
- package/dist/pubid-urn/schemas/bsi.d.ts +8 -0
- package/dist/pubid-urn/schemas/bsi.js +98 -0
- package/dist/pubid-urn/schemas/calconnect.d.ts +8 -0
- package/dist/pubid-urn/schemas/calconnect.js +16 -0
- package/dist/pubid-urn/schemas/ccsds.d.ts +8 -0
- package/dist/pubid-urn/schemas/ccsds.js +18 -0
- package/dist/pubid-urn/schemas/cencenelec.d.ts +8 -0
- package/dist/pubid-urn/schemas/cencenelec.js +39 -0
- package/dist/pubid-urn/schemas/cie.d.ts +8 -0
- package/dist/pubid-urn/schemas/cie.js +41 -0
- package/dist/pubid-urn/schemas/csa.d.ts +8 -0
- package/dist/pubid-urn/schemas/csa.js +45 -0
- package/dist/pubid-urn/schemas/doi.d.ts +8 -0
- package/dist/pubid-urn/schemas/doi.js +14 -0
- package/dist/pubid-urn/schemas/easc.d.ts +8 -0
- package/dist/pubid-urn/schemas/easc.js +16 -0
- package/dist/pubid-urn/schemas/ecma.d.ts +8 -0
- package/dist/pubid-urn/schemas/ecma.js +17 -0
- package/dist/pubid-urn/schemas/etsi.d.ts +8 -0
- package/dist/pubid-urn/schemas/etsi.js +23 -0
- package/dist/pubid-urn/schemas/evs.d.ts +8 -0
- package/dist/pubid-urn/schemas/evs.js +30 -0
- package/dist/pubid-urn/schemas/gb.d.ts +8 -0
- package/dist/pubid-urn/schemas/gb.js +18 -0
- package/dist/pubid-urn/schemas/gost.d.ts +8 -0
- package/dist/pubid-urn/schemas/gost.js +23 -0
- package/dist/pubid-urn/schemas/iala.d.ts +8 -0
- package/dist/pubid-urn/schemas/iala.js +26 -0
- package/dist/pubid-urn/schemas/iana.d.ts +8 -0
- package/dist/pubid-urn/schemas/iana.js +13 -0
- package/dist/pubid-urn/schemas/idf.d.ts +8 -0
- package/dist/pubid-urn/schemas/idf.js +34 -0
- package/dist/pubid-urn/schemas/iec.d.ts +8 -0
- package/dist/pubid-urn/schemas/iec.js +61 -0
- package/dist/pubid-urn/schemas/ieee.d.ts +8 -0
- package/dist/pubid-urn/schemas/ieee.js +116 -0
- package/dist/pubid-urn/schemas/ietf.d.ts +8 -0
- package/dist/pubid-urn/schemas/ietf.js +17 -0
- package/dist/pubid-urn/schemas/iho.d.ts +8 -0
- package/dist/pubid-urn/schemas/iho.js +35 -0
- package/dist/pubid-urn/schemas/index.d.ts +9 -0
- package/dist/pubid-urn/schemas/index.js +97 -0
- package/dist/pubid-urn/schemas/isbn.d.ts +8 -0
- package/dist/pubid-urn/schemas/isbn.js +14 -0
- package/dist/pubid-urn/schemas/iso.d.ts +8 -0
- package/dist/pubid-urn/schemas/iso.js +46 -0
- package/dist/pubid-urn/schemas/itu.d.ts +8 -0
- package/dist/pubid-urn/schemas/itu.js +61 -0
- package/dist/pubid-urn/schemas/jcgm.d.ts +8 -0
- package/dist/pubid-urn/schemas/jcgm.js +21 -0
- package/dist/pubid-urn/schemas/jis.d.ts +8 -0
- package/dist/pubid-urn/schemas/jis.js +24 -0
- package/dist/pubid-urn/schemas/nist.d.ts +8 -0
- package/dist/pubid-urn/schemas/nist.js +90 -0
- package/dist/pubid-urn/schemas/oasis.d.ts +8 -0
- package/dist/pubid-urn/schemas/oasis.js +17 -0
- package/dist/pubid-urn/schemas/ogc.d.ts +8 -0
- package/dist/pubid-urn/schemas/ogc.js +14 -0
- package/dist/pubid-urn/schemas/oiml.d.ts +8 -0
- package/dist/pubid-urn/schemas/oiml.js +39 -0
- package/dist/pubid-urn/schemas/omg.d.ts +8 -0
- package/dist/pubid-urn/schemas/omg.js +15 -0
- package/dist/pubid-urn/schemas/plateau.d.ts +8 -0
- package/dist/pubid-urn/schemas/plateau.js +20 -0
- package/dist/pubid-urn/schemas/sae.d.ts +8 -0
- package/dist/pubid-urn/schemas/sae.js +18 -0
- package/dist/pubid-urn/schemas/tgpp.d.ts +8 -0
- package/dist/pubid-urn/schemas/tgpp.js +17 -0
- package/dist/pubid-urn/schemas/un.d.ts +8 -0
- package/dist/pubid-urn/schemas/un.js +13 -0
- package/dist/pubid-urn/schemas/w3c.d.ts +8 -0
- package/dist/pubid-urn/schemas/w3c.js +23 -0
- package/dist/pubid-urn/schemas/xsf.d.ts +8 -0
- package/dist/pubid-urn/schemas/xsf.js +12 -0
- package/dist/pubid-urn/slug.d.ts +20 -0
- package/dist/pubid-urn/slug.js +47 -0
- package/package.json +2 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pubid-scheme URN (pubid#656) — the TS mirror of
|
|
3
|
+
* Pubid::PubidUrn (lib/pubid/pubid_urn.rb). Pubid's own URN namespace,
|
|
4
|
+
* which carries the full typed model and always round-trips —
|
|
5
|
+
* `parsePubidUrn(generate(id.toHash())).toHash == id.toHash`.
|
|
6
|
+
*
|
|
7
|
+
* It is a different thing from the official URN (`toUrn()`, e.g.
|
|
8
|
+
* "urn:iso:std:…"), which follows the registered grammar of the
|
|
9
|
+
* organization and may be lossy. relaton uses the pubid-scheme URN as
|
|
10
|
+
* its HTTP path slug (relaton/relaton#441), so the form is also
|
|
11
|
+
* path-safe: no "/", "%", "?", "#" and no empty segment.
|
|
12
|
+
*
|
|
13
|
+
* urn:pubid:iso:is:9001:year-2015:amd:1:year-2016
|
|
14
|
+
*
|
|
15
|
+
* The shape of each identifier type is declared in its flavor's schema
|
|
16
|
+
* (src/pubid-urn/schemas/<flavor>.ts), through {@linkcode define}. A
|
|
17
|
+
* flavor with no schema cannot emit one yet.
|
|
18
|
+
*/
|
|
19
|
+
import { FlavorSchema } from "./schema.js";
|
|
20
|
+
import { InvalidInputError, UrnParseError } from "./errors.js";
|
|
21
|
+
import { Encoder } from "./encoder.js";
|
|
22
|
+
import { Decoder } from "./decoder.js";
|
|
23
|
+
import { exactMatch } from "./escape.js";
|
|
24
|
+
import { PUBID_URN_SCHEMAS } from "./schemas/index.js";
|
|
25
|
+
import { resolveType } from "../model/identifier.js";
|
|
26
|
+
import { toSlug, fromSlug } from "./slug.js";
|
|
27
|
+
export { toSlug, fromSlug };
|
|
28
|
+
export { Atom, Facet, FlavorSchema, TypeSchema } from "./schema.js";
|
|
29
|
+
export { Encoder } from "./encoder.js";
|
|
30
|
+
export { Decoder } from "./decoder.js";
|
|
31
|
+
export { UrnParseError, InvalidInputError } from "./errors.js";
|
|
32
|
+
export { encode, force, decode } from "./escape.js";
|
|
33
|
+
export { take, put, blank } from "./paths.js";
|
|
34
|
+
export const PREFIX = "urn:pubid:";
|
|
35
|
+
/** An absent core value in the middle of the core, or an absent element
|
|
36
|
+
* of a list. Distinct from an escape, which always has two hex digits. */
|
|
37
|
+
export const NULL = "~";
|
|
38
|
+
/** Pubid::MAX_INPUT_LENGTH (1000) — the printed-identifier guard. */
|
|
39
|
+
export const MAX_INPUT_LENGTH = 1000;
|
|
40
|
+
export const MAX_LENGTH = 16 * MAX_INPUT_LENGTH;
|
|
41
|
+
const INPUT_NOT_A_STRING_MESSAGE = "input is not a String";
|
|
42
|
+
/**
|
|
43
|
+
* A raw segment that reads as structure and not as a core value: a type
|
|
44
|
+
* token or the facet marker ("amd", "f"), an atom ("year-2015"), or a
|
|
45
|
+
* token of another flavor ("iec.is", "3gpp.ts").
|
|
46
|
+
*
|
|
47
|
+
* The rule depends on the SHAPE of the segment only, never on which
|
|
48
|
+
* schemas exist. So a published URN keeps its meaning when a flavor, a
|
|
49
|
+
* type token or an atom is added later. Type tokens, atom prefixes and
|
|
50
|
+
* facet names therefore start with a lower-case letter
|
|
51
|
+
* (FlavorSchema.NAME). Digit-led numbers ("9001", "802.11", "800-53")
|
|
52
|
+
* and upper-case ones ("G.711") never match; a core value that does
|
|
53
|
+
* match gets its first character escaped ("iec.is" -> "~69ec.is").
|
|
54
|
+
*/
|
|
55
|
+
const STRUCTURE_HEAD = /^[a-z][a-z0-9]*/;
|
|
56
|
+
const STRUCTURE_FLAVOR_TOKEN = /^[a-z0-9]*[a-z][a-z0-9]*\.[a-z][a-z0-9]*$/;
|
|
57
|
+
/** The schema registry: flavor segment => its FlavorSchema. Built lazily
|
|
58
|
+
* from the declarative tables in ./schemas/ (no import side effects). */
|
|
59
|
+
let registry;
|
|
60
|
+
function schemas() {
|
|
61
|
+
if (registry === undefined) {
|
|
62
|
+
registry = new Map(PUBID_URN_SCHEMAS.map((schema) => [schema.flavor, schema]));
|
|
63
|
+
}
|
|
64
|
+
return registry;
|
|
65
|
+
}
|
|
66
|
+
/** Declare the schema of a flavor. `flavor` is the flavor segment of its
|
|
67
|
+
* `_type` ("iso", "cencenelec", "3gpp"). The callback plays ruby's
|
|
68
|
+
* instance_eval: it receives the schema and declares atoms/facets/types. */
|
|
69
|
+
export function define(flavor, body) {
|
|
70
|
+
const schema = new FlavorSchema(flavor);
|
|
71
|
+
body(schema);
|
|
72
|
+
return schema;
|
|
73
|
+
}
|
|
74
|
+
/** The pubid-scheme URN of an identifier hash (ruby generate(identifier):
|
|
75
|
+
* the hash is Identifier#to_hash's output). */
|
|
76
|
+
export function generate(hash) {
|
|
77
|
+
const urn = PREFIX + new Encoder(hash).body();
|
|
78
|
+
if (urn.length > MAX_LENGTH) {
|
|
79
|
+
throw new InvalidInputError(`pubid URN exceeds ${MAX_LENGTH} characters`);
|
|
80
|
+
}
|
|
81
|
+
return urn;
|
|
82
|
+
}
|
|
83
|
+
/** Parse a pubid-scheme URN back into an identifier, through the
|
|
84
|
+
* canonical gate: the decoded identifier must re-encode to exactly the
|
|
85
|
+
* input, so one identifier has exactly one URN. */
|
|
86
|
+
export function parse(urn) {
|
|
87
|
+
checkInput(urn);
|
|
88
|
+
const identifier = build(urn.slice(PREFIX.length));
|
|
89
|
+
checkCanonical(identifier, urn);
|
|
90
|
+
return identifier;
|
|
91
|
+
}
|
|
92
|
+
/** True for "urn:pubid:…" (the NID is case-insensitive, RFC 8141). */
|
|
93
|
+
export function isPubidUrn(urn) {
|
|
94
|
+
return urn.slice(0, PREFIX.length).toLowerCase() === PREFIX;
|
|
95
|
+
}
|
|
96
|
+
/** "iso" in "pubid:iso:technical-report". */
|
|
97
|
+
export function flavorSegment(type) {
|
|
98
|
+
return typeof type === "string" ? type.split(":")[1] : undefined;
|
|
99
|
+
}
|
|
100
|
+
export function flavorSchema(flavor) {
|
|
101
|
+
return schemas().get(flavor);
|
|
102
|
+
}
|
|
103
|
+
/** @raise InvalidInputError when no schema places `type`. */
|
|
104
|
+
export function typeSchemaOrThrow(type) {
|
|
105
|
+
const flavor = flavorSegment(type);
|
|
106
|
+
const schema = flavor !== undefined ? flavorSchema(flavor)?.forType(type) : undefined;
|
|
107
|
+
if (schema !== undefined)
|
|
108
|
+
return schema;
|
|
109
|
+
throw new InvalidInputError(`no pubid URN schema for ${JSON.stringify(type)}`);
|
|
110
|
+
}
|
|
111
|
+
/** Does `token` (a raw, still escaped segment) read as structure? */
|
|
112
|
+
export function structural(token) {
|
|
113
|
+
const head = STRUCTURE_HEAD.exec(token);
|
|
114
|
+
if (head !== null) {
|
|
115
|
+
const len = head[0].length;
|
|
116
|
+
if (len === token.length || token[len] === "-")
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
const flavorToken = STRUCTURE_FLAVOR_TOKEN.exec(token);
|
|
120
|
+
return flavorToken !== null && flavorToken[0].length === token.length;
|
|
121
|
+
}
|
|
122
|
+
/** A type token is "<token>" of the URN's own flavor, or
|
|
123
|
+
* "<flavor>.<token>" for a node of another flavor. */
|
|
124
|
+
export function splitToken(token, flavor) {
|
|
125
|
+
return token.includes(".") ? token.split(".", 2) : [flavor, token];
|
|
126
|
+
}
|
|
127
|
+
/** Split `body` at `separator` outside brackets. `allowEmpty` is true for
|
|
128
|
+
* the items of a list, where an empty item is an empty String; a ":"
|
|
129
|
+
* segment is never empty. */
|
|
130
|
+
export function splitTop(body, separator, allowEmpty = false) {
|
|
131
|
+
const parts = [];
|
|
132
|
+
let depth = 0;
|
|
133
|
+
let current = "";
|
|
134
|
+
for (const char of body) {
|
|
135
|
+
if (char === "(")
|
|
136
|
+
depth += 1;
|
|
137
|
+
if (char === ")") {
|
|
138
|
+
depth -= 1;
|
|
139
|
+
if (depth < 0)
|
|
140
|
+
unbalanced(body);
|
|
141
|
+
}
|
|
142
|
+
if (char === separator && depth === 0) {
|
|
143
|
+
parts.push(current);
|
|
144
|
+
current = "";
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
current += char;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (depth !== 0)
|
|
151
|
+
unbalanced(body);
|
|
152
|
+
parts.push(current);
|
|
153
|
+
if (!allowEmpty && parts.some((part) => part === "")) {
|
|
154
|
+
throw new UrnParseError(`empty segment in ${JSON.stringify(body)}`);
|
|
155
|
+
}
|
|
156
|
+
return parts;
|
|
157
|
+
}
|
|
158
|
+
function unbalanced(body) {
|
|
159
|
+
throw new UrnParseError(`unbalanced bracket in ${JSON.stringify(body)}`);
|
|
160
|
+
}
|
|
161
|
+
function checkInput(urn) {
|
|
162
|
+
if (typeof urn !== "string") {
|
|
163
|
+
throw new InvalidInputError(INPUT_NOT_A_STRING_MESSAGE);
|
|
164
|
+
}
|
|
165
|
+
if (urn.length > MAX_LENGTH) {
|
|
166
|
+
throw new InvalidInputError(`input exceeds ${MAX_LENGTH} characters`);
|
|
167
|
+
}
|
|
168
|
+
// ruby also rejects a String with invalid byte encoding; a JS string is
|
|
169
|
+
// always valid UTF-16, so that half of the check has no counterpart.
|
|
170
|
+
if (!isPubidUrn(urn)) {
|
|
171
|
+
throw new UrnParseError(`not a pubid-scheme URN: ${JSON.stringify(urn)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** The URN is untrusted input: a value that decodes but cannot be cast
|
|
175
|
+
* into its attribute (a bad boolean, a String where a component goes)
|
|
176
|
+
* is a bad URN, so every failure here is reported as UrnParseError,
|
|
177
|
+
* with the original error as its cause. */
|
|
178
|
+
function build(body) {
|
|
179
|
+
let hash;
|
|
180
|
+
let built;
|
|
181
|
+
try {
|
|
182
|
+
hash = new Decoder(body).toIdentifierHash();
|
|
183
|
+
const klass = resolveType(hash["_type"]);
|
|
184
|
+
if (klass === undefined) {
|
|
185
|
+
throw new InvalidInputError(`unknown identifier type ${JSON.stringify(hash["_type"])}`);
|
|
186
|
+
}
|
|
187
|
+
built = klass.fromHash(hash);
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
if (e instanceof UrnParseError)
|
|
191
|
+
throw e;
|
|
192
|
+
throw new UrnParseError(`pubid URN ${JSON.stringify(body)} does not build: ${errorMessage(e)}`);
|
|
193
|
+
}
|
|
194
|
+
return built;
|
|
195
|
+
}
|
|
196
|
+
function checkCanonical(identifier, urn) {
|
|
197
|
+
let canonical;
|
|
198
|
+
try {
|
|
199
|
+
canonical = generate(identifier.toHash());
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
if (e instanceof InvalidInputError) {
|
|
203
|
+
throw new UrnParseError(`${JSON.stringify(urn)} does not re-encode: ${errorMessage(e)}`);
|
|
204
|
+
}
|
|
205
|
+
throw e;
|
|
206
|
+
}
|
|
207
|
+
if (canonical.slice(PREFIX.length) !== urn.slice(PREFIX.length)) {
|
|
208
|
+
throw new UrnParseError(`${JSON.stringify(urn)} is not canonical; the canonical form is ${JSON.stringify(canonical)}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function errorMessage(e) {
|
|
212
|
+
return e instanceof Error ? e.message : String(e);
|
|
213
|
+
}
|
|
214
|
+
// exactMatch is re-exported for the schemas' own probes; the module
|
|
215
|
+
// surface mirrors lib/pubid/pubid_urn.rb's helpers.
|
|
216
|
+
export { exactMatch };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads and writes a dotted key path ("edition.number",
|
|
3
|
+
* "languages.*.code") in an identifier hash — the TS mirror of
|
|
4
|
+
* Pubid::PubidUrn::Paths (lib/pubid/pubid_urn/paths.rb).
|
|
5
|
+
*
|
|
6
|
+
* `take` removes what it reads, so after every core path, atom and facet
|
|
7
|
+
* has been taken, anything left in the node is a key that the schema
|
|
8
|
+
* does not place — the encoder refuses it rather than drop it silently.
|
|
9
|
+
*
|
|
10
|
+
* A degenerate component may serialize as a bare scalar ("edition" =>
|
|
11
|
+
* "2" rather than {"number" => "2"}, see FLAT_SCALAR_FIELDS). Where a
|
|
12
|
+
* path expects a Hash and finds a scalar, the scalar IS the value of
|
|
13
|
+
* the path. `put` always writes the nested form, which fromHash reads
|
|
14
|
+
* as well, so the rebuilt identifier serializes alike.
|
|
15
|
+
*
|
|
16
|
+
* ruby nil is the JS null/undefined pair: the corpus wire form is JSON,
|
|
17
|
+
* where a nil attribute arrives as null; isNil() is the shared test.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isNil(value: unknown): boolean;
|
|
20
|
+
/** @return the value, or null (ruby nil) when the path is absent. */
|
|
21
|
+
export declare function take(container: Record<string, unknown>, parts: string[]): unknown;
|
|
22
|
+
/** Write `value` (null writes nothing). */
|
|
23
|
+
export declare function put(container: Record<string, unknown>, parts: string[], value: unknown): void;
|
|
24
|
+
/** True when nothing but empty containers is left. */
|
|
25
|
+
export declare function blank(value: unknown): boolean;
|
|
26
|
+
export declare function deepDup(value: unknown): unknown;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads and writes a dotted key path ("edition.number",
|
|
3
|
+
* "languages.*.code") in an identifier hash — the TS mirror of
|
|
4
|
+
* Pubid::PubidUrn::Paths (lib/pubid/pubid_urn/paths.rb).
|
|
5
|
+
*
|
|
6
|
+
* `take` removes what it reads, so after every core path, atom and facet
|
|
7
|
+
* has been taken, anything left in the node is a key that the schema
|
|
8
|
+
* does not place — the encoder refuses it rather than drop it silently.
|
|
9
|
+
*
|
|
10
|
+
* A degenerate component may serialize as a bare scalar ("edition" =>
|
|
11
|
+
* "2" rather than {"number" => "2"}, see FLAT_SCALAR_FIELDS). Where a
|
|
12
|
+
* path expects a Hash and finds a scalar, the scalar IS the value of
|
|
13
|
+
* the path. `put` always writes the nested form, which fromHash reads
|
|
14
|
+
* as well, so the rebuilt identifier serializes alike.
|
|
15
|
+
*
|
|
16
|
+
* ruby nil is the JS null/undefined pair: the corpus wire form is JSON,
|
|
17
|
+
* where a nil attribute arrives as null; isNil() is the shared test.
|
|
18
|
+
*/
|
|
19
|
+
export function isNil(value) {
|
|
20
|
+
return value === undefined || value === null;
|
|
21
|
+
}
|
|
22
|
+
/** @return the value, or null (ruby nil) when the path is absent. */
|
|
23
|
+
export function take(container, parts) {
|
|
24
|
+
if (container === null || typeof container !== "object" || Array.isArray(container)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const key = parts[0];
|
|
28
|
+
if (!Object.hasOwn(container, key))
|
|
29
|
+
return null;
|
|
30
|
+
const rest = parts.slice(1);
|
|
31
|
+
const value = container[key];
|
|
32
|
+
if (rest.length === 0) {
|
|
33
|
+
delete container[key];
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
if (rest[0] === "*") {
|
|
37
|
+
return takeElements(container, key, value, rest.slice(1));
|
|
38
|
+
}
|
|
39
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
40
|
+
return take(value, rest);
|
|
41
|
+
}
|
|
42
|
+
if (rest.length === 1) {
|
|
43
|
+
delete container[key];
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function takeElements(container, key, value, rest) {
|
|
49
|
+
if (!Array.isArray(value))
|
|
50
|
+
return null;
|
|
51
|
+
if (rest.length === 0) {
|
|
52
|
+
delete container[key];
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
const values = value.map((element, i) => {
|
|
56
|
+
if (element !== null && typeof element === "object" && !Array.isArray(element)) {
|
|
57
|
+
return take(element, rest);
|
|
58
|
+
}
|
|
59
|
+
if (rest.length === 1 && element !== null && element !== undefined) {
|
|
60
|
+
value[i] = {};
|
|
61
|
+
return element;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
});
|
|
65
|
+
return values.every((v) => v === null) ? null : values;
|
|
66
|
+
}
|
|
67
|
+
/** Write `value` (null writes nothing). */
|
|
68
|
+
export function put(container, parts, value) {
|
|
69
|
+
if (value === undefined || value === null)
|
|
70
|
+
return;
|
|
71
|
+
const key = parts[0];
|
|
72
|
+
const rest = parts.slice(1);
|
|
73
|
+
if (rest.length === 0) {
|
|
74
|
+
container[key] = value;
|
|
75
|
+
}
|
|
76
|
+
else if (rest[0] === "*") {
|
|
77
|
+
putElements(container, key, value, rest.slice(1));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
let nested = container[key];
|
|
81
|
+
if (nested === undefined || nested === null) {
|
|
82
|
+
nested = {};
|
|
83
|
+
container[key] = nested;
|
|
84
|
+
}
|
|
85
|
+
put(nested, rest, value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function putElements(container, key, values, rest) {
|
|
89
|
+
if (rest.length === 0) {
|
|
90
|
+
container[key] = values;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let elements = container[key];
|
|
94
|
+
if (elements === undefined || elements === null || !Array.isArray(elements)) {
|
|
95
|
+
elements = [];
|
|
96
|
+
container[key] = elements;
|
|
97
|
+
}
|
|
98
|
+
values.forEach((value, i) => {
|
|
99
|
+
if (value === undefined || value === null)
|
|
100
|
+
return;
|
|
101
|
+
let element = elements[i];
|
|
102
|
+
if (element === undefined || element === null || typeof element !== "object" || Array.isArray(element)) {
|
|
103
|
+
element = {};
|
|
104
|
+
elements[i] = element;
|
|
105
|
+
}
|
|
106
|
+
put(element, rest, value);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/** True when nothing but empty containers is left. */
|
|
110
|
+
export function blank(value) {
|
|
111
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
112
|
+
return Object.values(value).every((v) => blank(v));
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
return value.every((v) => blank(v));
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
export function deepDup(value) {
|
|
120
|
+
if (value !== null && typeof value === "object") {
|
|
121
|
+
if (Array.isArray(value))
|
|
122
|
+
return value.map((v) => deepDup(v));
|
|
123
|
+
const out = {};
|
|
124
|
+
for (const [k, v] of Object.entries(value))
|
|
125
|
+
out[k] = deepDup(v);
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The schema definition DSL of the pubid-scheme URN — the TS mirror of
|
|
3
|
+
* Pubid::PubidUrn::Atom/Facet/TypeSchema/FlavorSchema
|
|
4
|
+
* (lib/pubid/pubid_urn/schema.rb).
|
|
5
|
+
*
|
|
6
|
+
* One prefixed atom: an optional value, found by its prefix and not by
|
|
7
|
+
* its position ("part-1", "co-IEC,IEEE", "ids-(iso:is:9001)"). A facet
|
|
8
|
+
* is a spelling-only value, written in the trailing "f" group.
|
|
9
|
+
*
|
|
10
|
+
* The URN shape of one concrete identifier type is a TypeSchema: the
|
|
11
|
+
* positional core, the atoms in their canonical order, the facets. A
|
|
12
|
+
* flavor's FlavorSchema declares flavor-level atoms/facets that apply
|
|
13
|
+
* to every type, plus one TypeSchema per concrete type.
|
|
14
|
+
*/
|
|
15
|
+
export type AtomKind = "string" | "list" | "bool" | "bools" | "int" | "id" | "ids";
|
|
16
|
+
export type FacetKind = "string" | "list" | "bool" | "bools";
|
|
17
|
+
export type CoreKind = "string" | "int";
|
|
18
|
+
export declare class Atom {
|
|
19
|
+
readonly path: string;
|
|
20
|
+
readonly prefix: string;
|
|
21
|
+
readonly kind: AtomKind;
|
|
22
|
+
readonly parts: readonly string[];
|
|
23
|
+
constructor(path: string, prefix: string, kind: AtomKind);
|
|
24
|
+
get list(): boolean;
|
|
25
|
+
/** The kind of one value (of one element of a list). */
|
|
26
|
+
get element(): "string" | "bool" | AtomKind;
|
|
27
|
+
}
|
|
28
|
+
export declare class Facet {
|
|
29
|
+
readonly path: string;
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly kind: FacetKind;
|
|
32
|
+
readonly parts: readonly string[];
|
|
33
|
+
constructor(path: string, name: string, kind: FacetKind);
|
|
34
|
+
get list(): boolean;
|
|
35
|
+
get element(): "string" | "bool" | FacetKind;
|
|
36
|
+
}
|
|
37
|
+
export type CoreSpec = string | [path: string, kind: CoreKind];
|
|
38
|
+
export type AtomSpec = [path: string, prefix: string, kind?: AtomKind];
|
|
39
|
+
export type FacetSpec = [path: string, name: string, kind?: FacetKind];
|
|
40
|
+
/** The URN shape of one concrete identifier type. */
|
|
41
|
+
export declare class TypeSchema {
|
|
42
|
+
readonly flavor: string;
|
|
43
|
+
readonly type: string;
|
|
44
|
+
readonly token: string;
|
|
45
|
+
readonly core: readonly (readonly string[])[];
|
|
46
|
+
readonly coreKinds: readonly CoreKind[];
|
|
47
|
+
readonly atoms: readonly Atom[];
|
|
48
|
+
readonly facets: readonly Facet[];
|
|
49
|
+
private readonly atomIndex;
|
|
50
|
+
private readonly facetNames;
|
|
51
|
+
constructor(flavor: string, type: string, token: string, core: readonly CoreSpec[], atoms: readonly Atom[], facets: readonly Facet[]);
|
|
52
|
+
/** The canonical position of the atom whose prefix starts `token`,
|
|
53
|
+
* or undefined when `token` is no atom of this type. */
|
|
54
|
+
atomPosition(token: string): number | undefined;
|
|
55
|
+
facet(name: string): Facet | undefined;
|
|
56
|
+
}
|
|
57
|
+
export interface TypeDeclaration {
|
|
58
|
+
/** The positional core paths; an entry is a path (a String value) or
|
|
59
|
+
* [path, kind], kind "string" or "int". Default ["number"]. */
|
|
60
|
+
core?: CoreSpec[];
|
|
61
|
+
/** Extra atoms: [path, prefix, kind?]. */
|
|
62
|
+
atoms?: AtomSpec[];
|
|
63
|
+
/** Extra facets: [path, name, kind?]. */
|
|
64
|
+
facets?: FacetSpec[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The pubid-scheme schema of one flavor: the canonical atom order, the
|
|
68
|
+
* facets, and one TypeSchema for each concrete type.
|
|
69
|
+
*
|
|
70
|
+
* Atoms and facets declared at flavor level apply to every type, in the
|
|
71
|
+
* order of declaration; a type adds its own after them. A type token, an
|
|
72
|
+
* atom prefix and a facet name are a lower-case letter followed by
|
|
73
|
+
* lower-case letters and digits, so none can contain the "-" that ends a
|
|
74
|
+
* prefix or the "." that qualifies a token of another flavor.
|
|
75
|
+
*/
|
|
76
|
+
export declare class FlavorSchema {
|
|
77
|
+
readonly flavor: string;
|
|
78
|
+
readonly types: Map<string, TypeSchema>;
|
|
79
|
+
private readonly tokens;
|
|
80
|
+
private atoms;
|
|
81
|
+
private facets;
|
|
82
|
+
constructor(flavor: string);
|
|
83
|
+
atom(path: string, prefix: string, kind?: AtomKind): void;
|
|
84
|
+
facet(path: string, name: string, kind?: FacetKind): void;
|
|
85
|
+
type(type: string, token: string, declaration?: TypeDeclaration): void;
|
|
86
|
+
forToken(token: string): TypeSchema | undefined;
|
|
87
|
+
forType(type: string): TypeSchema | undefined;
|
|
88
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The schema definition DSL of the pubid-scheme URN — the TS mirror of
|
|
3
|
+
* Pubid::PubidUrn::Atom/Facet/TypeSchema/FlavorSchema
|
|
4
|
+
* (lib/pubid/pubid_urn/schema.rb).
|
|
5
|
+
*
|
|
6
|
+
* One prefixed atom: an optional value, found by its prefix and not by
|
|
7
|
+
* its position ("part-1", "co-IEC,IEEE", "ids-(iso:is:9001)"). A facet
|
|
8
|
+
* is a spelling-only value, written in the trailing "f" group.
|
|
9
|
+
*
|
|
10
|
+
* The URN shape of one concrete identifier type is a TypeSchema: the
|
|
11
|
+
* positional core, the atoms in their canonical order, the facets. A
|
|
12
|
+
* flavor's FlavorSchema declares flavor-level atoms/facets that apply
|
|
13
|
+
* to every type, plus one TypeSchema per concrete type.
|
|
14
|
+
*/
|
|
15
|
+
/** A list kind => the kind of its elements. */
|
|
16
|
+
const LIST_KINDS = {
|
|
17
|
+
list: "string",
|
|
18
|
+
bools: "bool",
|
|
19
|
+
};
|
|
20
|
+
export class Atom {
|
|
21
|
+
path;
|
|
22
|
+
prefix;
|
|
23
|
+
kind;
|
|
24
|
+
parts;
|
|
25
|
+
constructor(path, prefix, kind) {
|
|
26
|
+
this.path = path;
|
|
27
|
+
this.prefix = prefix;
|
|
28
|
+
this.kind = kind;
|
|
29
|
+
this.parts = path.split(".");
|
|
30
|
+
}
|
|
31
|
+
get list() {
|
|
32
|
+
return LIST_KINDS[this.kind] !== undefined;
|
|
33
|
+
}
|
|
34
|
+
/** The kind of one value (of one element of a list). */
|
|
35
|
+
get element() {
|
|
36
|
+
return LIST_KINDS[this.kind] ?? this.kind;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export class Facet {
|
|
40
|
+
path;
|
|
41
|
+
name;
|
|
42
|
+
kind;
|
|
43
|
+
parts;
|
|
44
|
+
constructor(path, name, kind) {
|
|
45
|
+
this.path = path;
|
|
46
|
+
this.name = name;
|
|
47
|
+
this.kind = kind;
|
|
48
|
+
this.parts = path.split(".");
|
|
49
|
+
}
|
|
50
|
+
get list() {
|
|
51
|
+
return LIST_KINDS[this.kind] !== undefined;
|
|
52
|
+
}
|
|
53
|
+
get element() {
|
|
54
|
+
return LIST_KINDS[this.kind] ?? this.kind;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** The URN shape of one concrete identifier type. */
|
|
58
|
+
export class TypeSchema {
|
|
59
|
+
flavor;
|
|
60
|
+
type;
|
|
61
|
+
token;
|
|
62
|
+
core;
|
|
63
|
+
coreKinds;
|
|
64
|
+
atoms;
|
|
65
|
+
facets;
|
|
66
|
+
atomIndex;
|
|
67
|
+
facetNames;
|
|
68
|
+
constructor(flavor, type, token, core, atoms, facets) {
|
|
69
|
+
this.flavor = flavor;
|
|
70
|
+
this.type = type;
|
|
71
|
+
this.token = token;
|
|
72
|
+
this.core = core.map((spec) => (typeof spec === "string" ? spec : spec[0]).split("."));
|
|
73
|
+
this.coreKinds = core.map((spec) => (typeof spec === "string" ? "string" : spec[1]));
|
|
74
|
+
this.atoms = atoms;
|
|
75
|
+
this.facets = facets;
|
|
76
|
+
this.atomIndex = new Map(atoms.map((atom, i) => [atom.prefix, i]));
|
|
77
|
+
this.facetNames = new Map(facets.map((facet) => [facet.name, facet]));
|
|
78
|
+
}
|
|
79
|
+
/** The canonical position of the atom whose prefix starts `token`,
|
|
80
|
+
* or undefined when `token` is no atom of this type. */
|
|
81
|
+
atomPosition(token) {
|
|
82
|
+
const m = /^([a-z0-9]+)-/.exec(token);
|
|
83
|
+
if (m === null)
|
|
84
|
+
return undefined;
|
|
85
|
+
return this.atomIndex.get(m[1]);
|
|
86
|
+
}
|
|
87
|
+
facet(name) {
|
|
88
|
+
return this.facetNames.get(name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const NAME = /^[a-z][a-z0-9]*$/;
|
|
92
|
+
const RESERVED_TOKENS = new Set(["f", "type"]);
|
|
93
|
+
/**
|
|
94
|
+
* The pubid-scheme schema of one flavor: the canonical atom order, the
|
|
95
|
+
* facets, and one TypeSchema for each concrete type.
|
|
96
|
+
*
|
|
97
|
+
* Atoms and facets declared at flavor level apply to every type, in the
|
|
98
|
+
* order of declaration; a type adds its own after them. A type token, an
|
|
99
|
+
* atom prefix and a facet name are a lower-case letter followed by
|
|
100
|
+
* lower-case letters and digits, so none can contain the "-" that ends a
|
|
101
|
+
* prefix or the "." that qualifies a token of another flavor.
|
|
102
|
+
*/
|
|
103
|
+
export class FlavorSchema {
|
|
104
|
+
flavor;
|
|
105
|
+
types = new Map();
|
|
106
|
+
tokens = new Map();
|
|
107
|
+
atoms = [];
|
|
108
|
+
facets = [];
|
|
109
|
+
constructor(flavor) {
|
|
110
|
+
this.flavor = flavor;
|
|
111
|
+
}
|
|
112
|
+
atom(path, prefix, kind) {
|
|
113
|
+
this.atoms.push(buildAtom(path, prefix, kind));
|
|
114
|
+
}
|
|
115
|
+
facet(path, name, kind) {
|
|
116
|
+
this.facets.push(buildFacet(path, name, kind));
|
|
117
|
+
}
|
|
118
|
+
type(type, token, declaration = {}) {
|
|
119
|
+
checkName(token, "type token");
|
|
120
|
+
if (RESERVED_TOKENS.has(token) || this.tokens.has(token)) {
|
|
121
|
+
throw new Error(`type token ${JSON.stringify(token)} is reserved or taken`);
|
|
122
|
+
}
|
|
123
|
+
const core = (declaration.core ?? ["number"]).map(buildCore);
|
|
124
|
+
const allAtoms = [...this.atoms, ...(declaration.atoms ?? []).map((spec) => buildAtom(...spec))];
|
|
125
|
+
checkUnique(allAtoms.map((atom) => atom.prefix), "atom prefix", type);
|
|
126
|
+
const allFacets = [
|
|
127
|
+
...this.facets,
|
|
128
|
+
...(declaration.facets ?? []).map((spec) => buildFacet(...spec)),
|
|
129
|
+
];
|
|
130
|
+
checkUnique(allFacets.map((facet) => facet.name), "facet name", type);
|
|
131
|
+
const schema = new TypeSchema(this.flavor, type, token, core, allAtoms, allFacets);
|
|
132
|
+
this.types.set(type, schema);
|
|
133
|
+
this.tokens.set(token, schema);
|
|
134
|
+
}
|
|
135
|
+
forToken(token) {
|
|
136
|
+
return this.tokens.get(token);
|
|
137
|
+
}
|
|
138
|
+
forType(type) {
|
|
139
|
+
return this.types.get(type);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function buildCore(spec) {
|
|
143
|
+
const kind = typeof spec === "string" ? "string" : spec[1];
|
|
144
|
+
if (kind !== "string" && kind !== "int") {
|
|
145
|
+
throw new Error(`unknown core kind ${JSON.stringify(kind)}`);
|
|
146
|
+
}
|
|
147
|
+
return spec;
|
|
148
|
+
}
|
|
149
|
+
function buildAtom(path, prefix, kind) {
|
|
150
|
+
checkName(prefix, "atom prefix");
|
|
151
|
+
return new Atom(path, prefix, checkKind(path, kind, ATOM_KINDS, "atom"));
|
|
152
|
+
}
|
|
153
|
+
function buildFacet(path, name, kind) {
|
|
154
|
+
checkName(name, "facet name");
|
|
155
|
+
return new Facet(path, name, checkKind(path, kind, FACET_KINDS, "facet"));
|
|
156
|
+
}
|
|
157
|
+
const ATOM_KINDS = ["string", "list", "bool", "bools", "int", "id", "ids"];
|
|
158
|
+
const FACET_KINDS = ["string", "list", "bool", "bools"];
|
|
159
|
+
/** A path with "*" reads a list, so its kind must be a list kind. */
|
|
160
|
+
function checkKind(path, kind, kinds, what) {
|
|
161
|
+
const listed = path.split(".").includes("*");
|
|
162
|
+
const resolved = kind ?? (listed ? "list" : "string");
|
|
163
|
+
if (!kinds.includes(resolved)) {
|
|
164
|
+
throw new Error(`unknown ${what} kind ${JSON.stringify(resolved)}`);
|
|
165
|
+
}
|
|
166
|
+
if (listed && LIST_KINDS[resolved] === undefined) {
|
|
167
|
+
throw new Error(`${what} ${JSON.stringify(path)} reads a list, so its kind must be one of list, bools`);
|
|
168
|
+
}
|
|
169
|
+
return resolved;
|
|
170
|
+
}
|
|
171
|
+
function checkName(name, what) {
|
|
172
|
+
const m = NAME.exec(name);
|
|
173
|
+
if (m === null || m[0].length !== name.length) {
|
|
174
|
+
throw new Error(`${what} ${JSON.stringify(name)} must match ${NAME.source}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function checkUnique(names, what, type) {
|
|
178
|
+
const counts = new Map();
|
|
179
|
+
for (const name of names)
|
|
180
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
181
|
+
for (const [name, count] of counts) {
|
|
182
|
+
if (count > 1) {
|
|
183
|
+
throw new Error(`${what} ${JSON.stringify(name)} repeats in ${type}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pubid-scheme URN schema of ADOBE — 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 adobeSchema: import("../schema.js").FlavorSchema;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pubid-scheme URN schema of ADOBE — 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 adobeSchema = define("adobe", (s) => {
|
|
10
|
+
s.atom("slug", "slug");
|
|
11
|
+
s.atom("version", "v");
|
|
12
|
+
s.type("pubid:adobe:identifier", "id", { core: [] });
|
|
13
|
+
s.type("pubid:adobe:publication", "pub", { core: [] });
|
|
14
|
+
s.type("pubid:adobe:tech-note", "technote");
|
|
15
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pubid-scheme URN schema of AMCA — 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 amcaSchema: import("../schema.js").FlavorSchema;
|