@pubid/pubid 0.1.32 → 0.1.34
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/conformance/implementation.d.ts +4 -0
- package/dist/flavors/bsi/model.js +13 -9
- package/dist/implementations/corpus-mode.d.ts +3 -0
- package/dist/implementations/corpus-mode.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/model/identifier.d.ts +6 -0
- package/dist/model/identifier.js +11 -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 +55 -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
- package/vendor/parg/nist.json +1 -1
package/artifact/corpus.json.gz
CHANGED
|
Binary file
|
|
@@ -16,6 +16,10 @@ export interface Identifier {
|
|
|
16
16
|
toHash(): Record<string, unknown>;
|
|
17
17
|
/** The canonical human spelling. */
|
|
18
18
|
toHuman(): string;
|
|
19
|
+
/** The renderer body every flavor writes; `toString()` delegates here. */
|
|
20
|
+
render(): string;
|
|
21
|
+
/** String coercion of the identifier — the canonical human spelling. */
|
|
22
|
+
toString(): string;
|
|
19
23
|
/** The URN; undefined when the flavor emits none. */
|
|
20
24
|
toUrn(): string | undefined;
|
|
21
25
|
/** Deserialize the canonical hash; the idempotency gate relies on it. */
|
|
@@ -776,15 +776,6 @@ export const ConsolidatedIdentifierClass = bsiClassWith({ kind: "consolidated-id
|
|
|
776
776
|
.replace(/ - TC$/, "")
|
|
777
777
|
.replace(/ PDF$/, "")
|
|
778
778
|
.replace(/ \([A-Z][a-z]+\)$/, "");
|
|
779
|
-
// The gem serializes only the `type` block onto the consolidated
|
|
780
|
-
// wrapper (the members carry their own full typed-stage trio).
|
|
781
|
-
ConsolidatedIdentifierClass.compactHash =
|
|
782
|
-
(model, hash) => {
|
|
783
|
-
const ts = model.typedStage;
|
|
784
|
-
if (ts === undefined || ts.code === "pubbs")
|
|
785
|
-
return;
|
|
786
|
-
hash["type"] = { name: ts.name, abbr: ts.abbr[0], type_code: ts.typeCode };
|
|
787
|
-
};
|
|
788
779
|
for (const supp of supplements) {
|
|
789
780
|
if (supp instanceof Amendment) {
|
|
790
781
|
if (supp.amd_suffix_form) {
|
|
@@ -838,6 +829,19 @@ export const ConsolidatedIdentifierClass = bsiClassWith({ kind: "consolidated-id
|
|
|
838
829
|
result += ` (R${ry})`;
|
|
839
830
|
return result;
|
|
840
831
|
});
|
|
832
|
+
// The gem serializes only the `type` block onto the consolidated
|
|
833
|
+
// wrapper (the members carry their own full typed-stage trio). This
|
|
834
|
+
// override is installed at load time, NOT inside the render closure: a
|
|
835
|
+
// fromHash -> toHash of a not-yet-rendered consolidated id must
|
|
836
|
+
// serialize identically (the pubid-scheme URN's canonical gate,
|
|
837
|
+
// pubid-ts#161, depends on it).
|
|
838
|
+
ConsolidatedIdentifierClass.compactHash =
|
|
839
|
+
(model, hash) => {
|
|
840
|
+
const ts = model.typedStage;
|
|
841
|
+
if (ts === undefined || ts.code === "pubbs")
|
|
842
|
+
return;
|
|
843
|
+
hash["type"] = { name: ts.name, abbr: ts.abbr[0], type_code: ts.typeCode };
|
|
844
|
+
};
|
|
841
845
|
// ConsolidatedIdentifier#to_urn override (renderer.rb class method):
|
|
842
846
|
// base URN plus per-supplement segments.
|
|
843
847
|
export function consolidatedUrn(id) {
|
|
@@ -20,6 +20,9 @@ export declare class CorpusBackedIdentifier implements Identifier {
|
|
|
20
20
|
constructor(row: CorpusRow, resolveHash: (hash: Record<string, unknown>) => CorpusRow);
|
|
21
21
|
toHash(): Record<string, unknown>;
|
|
22
22
|
toHuman(): string;
|
|
23
|
+
/** The corpus row's canonical human spelling — `toString()` delegates here. */
|
|
24
|
+
render(): string;
|
|
25
|
+
toString(): string;
|
|
23
26
|
toUrn(): string | undefined;
|
|
24
27
|
fromHash(hash: Record<string, unknown>): Identifier;
|
|
25
28
|
}
|
|
@@ -61,6 +61,13 @@ export class CorpusBackedIdentifier {
|
|
|
61
61
|
toHuman() {
|
|
62
62
|
return this.row.human;
|
|
63
63
|
}
|
|
64
|
+
/** The corpus row's canonical human spelling — `toString()` delegates here. */
|
|
65
|
+
render() {
|
|
66
|
+
return this.row.human;
|
|
67
|
+
}
|
|
68
|
+
toString() {
|
|
69
|
+
return this.row.human;
|
|
70
|
+
}
|
|
64
71
|
toUrn() {
|
|
65
72
|
return this.row.urn;
|
|
66
73
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,3 +8,4 @@ export { checkCase, runFlavor, runCorpus } from "./conformance/runner.js";
|
|
|
8
8
|
export { corpusModeImplementation, CorpusBackedIdentifier } from "./implementations/corpus-mode.js";
|
|
9
9
|
export { grammarImplementation } from "./flavors/index.js";
|
|
10
10
|
export { subsetMatch, valueMatch, exactMatch } from "./model/subset-match.js";
|
|
11
|
+
export * as PubidUrn from "./pubid-urn/index.js";
|
package/dist/index.js
CHANGED
|
@@ -8,3 +8,5 @@ export { corpusModeImplementation, CorpusBackedIdentifier } from "./implementati
|
|
|
8
8
|
// (pubid/pubid-ts#63 - the estate serving codec).
|
|
9
9
|
export { grammarImplementation } from "./flavors/index.js";
|
|
10
10
|
export { subsetMatch, valueMatch, exactMatch } from "./model/subset-match.js";
|
|
11
|
+
// The pubid-scheme URN codec and the slug form (pubid-ts#161).
|
|
12
|
+
export * as PubidUrn from "./pubid-urn/index.js";
|
|
@@ -53,8 +53,14 @@ export declare abstract class BaseIdentifier {
|
|
|
53
53
|
constructor(attrs?: Record<string, unknown>);
|
|
54
54
|
/** The human form (Ruby render(format: :human) → the flavor renderer). */
|
|
55
55
|
toHuman(): string;
|
|
56
|
+
/** String coercion answers the canonical human spelling — without this
|
|
57
|
+
* override `String(id)` and template literals print "[object Object]". */
|
|
58
|
+
toString(): string;
|
|
56
59
|
/** Ruby to_urn: the flavor's UrnGenerator, else the base template. */
|
|
57
60
|
toUrn(): string;
|
|
61
|
+
/** The pubid-scheme URN (pubid#656): the lossless form,
|
|
62
|
+
* `PubidUrn.parse(this.toPubidUrn()).toHash == this.toHash`. */
|
|
63
|
+
toPubidUrn(): string;
|
|
58
64
|
fromHash(hash: Record<string, unknown>): BaseIdentifier;
|
|
59
65
|
/** SubsetMatch: an all-parts reference skips the part attributes. */
|
|
60
66
|
subsetAllPartsWildcard(): boolean;
|
package/dist/model/identifier.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { FLAT_SCALAR_COMPONENTS, FLAT_SCALAR_FIELDS, BASE_ATTRIBUTES } from "./attribute.js";
|
|
21
21
|
import { Component } from "./component.js";
|
|
22
22
|
import { BaseUrnGenerator } from "./urn-generator.js";
|
|
23
|
+
import { generate as generatePubidUrn } from "../pubid-urn/index.js";
|
|
23
24
|
const TYPE_REGISTRY = new Map();
|
|
24
25
|
/** Register a concrete identifier class under its polymorphic _type (the *_TYPE_MAP role). */
|
|
25
26
|
export function registerType(klass) {
|
|
@@ -97,11 +98,21 @@ export class BaseIdentifier {
|
|
|
97
98
|
toHuman() {
|
|
98
99
|
return this.render();
|
|
99
100
|
}
|
|
101
|
+
/** String coercion answers the canonical human spelling — without this
|
|
102
|
+
* override `String(id)` and template literals print "[object Object]". */
|
|
103
|
+
toString() {
|
|
104
|
+
return this.render();
|
|
105
|
+
}
|
|
100
106
|
/** Ruby to_urn: the flavor's UrnGenerator, else the base template. */
|
|
101
107
|
toUrn() {
|
|
102
108
|
const Generator = this.constructor.urnGenerator ?? BaseUrnGenerator;
|
|
103
109
|
return new Generator(this).generate();
|
|
104
110
|
}
|
|
111
|
+
/** The pubid-scheme URN (pubid#656): the lossless form,
|
|
112
|
+
* `PubidUrn.parse(this.toPubidUrn()).toHash == this.toHash`. */
|
|
113
|
+
toPubidUrn() {
|
|
114
|
+
return generatePubidUrn(this.toHash());
|
|
115
|
+
}
|
|
105
116
|
fromHash(hash) {
|
|
106
117
|
return this.constructor.fromHash(hash);
|
|
107
118
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the body of a pubid-scheme URN back into an identifier hash —
|
|
3
|
+
* the TS mirror of Pubid::PubidUrn::Decoder (lib/pubid/pubid_urn/decoder.rb).
|
|
4
|
+
* The inverse of Encoder; strict about structure. Canonical form (atom
|
|
5
|
+
* order, escapes, trailing "~") is checked by PubidUrn.parse, which
|
|
6
|
+
* re-encodes the result and compares.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Decoder {
|
|
9
|
+
private readonly body;
|
|
10
|
+
constructor(body: string);
|
|
11
|
+
/** The identifier hash (`_type` and `base` included). */
|
|
12
|
+
toIdentifierHash(): Record<string, unknown>;
|
|
13
|
+
private decodeIdentifier;
|
|
14
|
+
private nodeSchema;
|
|
15
|
+
private decodeCore;
|
|
16
|
+
private coreValue;
|
|
17
|
+
private decodeAtoms;
|
|
18
|
+
private atomValue;
|
|
19
|
+
/** The value of a :string, :bool or list atom, or of a facet. */
|
|
20
|
+
private slotValue;
|
|
21
|
+
private scalar;
|
|
22
|
+
private boolean;
|
|
23
|
+
private integer;
|
|
24
|
+
private nested;
|
|
25
|
+
private decodeFacets;
|
|
26
|
+
private fail;
|
|
27
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the body of a pubid-scheme URN back into an identifier hash —
|
|
3
|
+
* the TS mirror of Pubid::PubidUrn::Decoder (lib/pubid/pubid_urn/decoder.rb).
|
|
4
|
+
* The inverse of Encoder; strict about structure. Canonical form (atom
|
|
5
|
+
* order, escapes, trailing "~") is checked by PubidUrn.parse, which
|
|
6
|
+
* re-encodes the result and compares.
|
|
7
|
+
*/
|
|
8
|
+
import { decode as escape, exactMatch } from "./escape.js";
|
|
9
|
+
import { UrnParseError } from "./errors.js";
|
|
10
|
+
import { put } from "./paths.js";
|
|
11
|
+
import { Decoder as _Decoder } from "./decoder.js";
|
|
12
|
+
import { flavorSchema, splitToken, splitTop, structural } from "./index.js";
|
|
13
|
+
const FACET = /^(\d+)\.([a-z0-9]+)-(.*)/;
|
|
14
|
+
const INTEGER = /^-?[0-9]+$/;
|
|
15
|
+
const NULL = "~";
|
|
16
|
+
export class Decoder {
|
|
17
|
+
body;
|
|
18
|
+
constructor(body) {
|
|
19
|
+
this.body = body;
|
|
20
|
+
}
|
|
21
|
+
/** The identifier hash (`_type` and `base` included). */
|
|
22
|
+
toIdentifierHash() {
|
|
23
|
+
const sep = this.body.indexOf(":");
|
|
24
|
+
const flavor = sep === -1 ? undefined : this.body.slice(0, sep);
|
|
25
|
+
const rest = sep === -1 ? undefined : this.body.slice(sep + 1);
|
|
26
|
+
if (rest === undefined || rest === "")
|
|
27
|
+
this.fail("no type segment");
|
|
28
|
+
return this.decodeIdentifier(rest, flavor);
|
|
29
|
+
}
|
|
30
|
+
decodeIdentifier(body, flavor) {
|
|
31
|
+
const tokens = splitTop(body, ":");
|
|
32
|
+
const nodes = [];
|
|
33
|
+
const schemas = [];
|
|
34
|
+
let i = 0;
|
|
35
|
+
while (i < tokens.length) {
|
|
36
|
+
const token = tokens[i];
|
|
37
|
+
if (token === "f") {
|
|
38
|
+
this.decodeFacets(tokens.slice(i + 1), nodes, schemas);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
const schema = this.nodeSchema(token, flavor);
|
|
42
|
+
const node = { _type: schema.type };
|
|
43
|
+
i = this.decodeCore(tokens, i + 1, node, schema);
|
|
44
|
+
i = this.decodeAtoms(tokens, i, node, schema);
|
|
45
|
+
nodes.push(node);
|
|
46
|
+
schemas.push(schema);
|
|
47
|
+
}
|
|
48
|
+
if (nodes.length === 0)
|
|
49
|
+
this.fail("no identifier");
|
|
50
|
+
for (let k = 1; k < nodes.length; k++) {
|
|
51
|
+
nodes[k]["base"] = nodes[k - 1];
|
|
52
|
+
}
|
|
53
|
+
return nodes[nodes.length - 1];
|
|
54
|
+
}
|
|
55
|
+
nodeSchema(token, flavor) {
|
|
56
|
+
if (token === "type") {
|
|
57
|
+
this.fail("type-metadata URN names a type, not a document");
|
|
58
|
+
}
|
|
59
|
+
let schema;
|
|
60
|
+
if (structural(token)) {
|
|
61
|
+
const [owner, name] = splitToken(token, flavor);
|
|
62
|
+
schema = flavorSchema(owner)?.forToken(name);
|
|
63
|
+
}
|
|
64
|
+
if (schema === undefined) {
|
|
65
|
+
this.fail(`unknown type or segment ${JSON.stringify(token)}`);
|
|
66
|
+
}
|
|
67
|
+
return schema;
|
|
68
|
+
}
|
|
69
|
+
decodeCore(tokens, index, node, schema) {
|
|
70
|
+
let i = index;
|
|
71
|
+
for (let ci = 0; ci < schema.core.length; ci++) {
|
|
72
|
+
if (i >= tokens.length)
|
|
73
|
+
break;
|
|
74
|
+
const token = tokens[i];
|
|
75
|
+
if (structural(token))
|
|
76
|
+
break;
|
|
77
|
+
if (token !== NULL) {
|
|
78
|
+
put(node, schema.core[ci], this.coreValue(token, schema.coreKinds[ci]));
|
|
79
|
+
}
|
|
80
|
+
i += 1;
|
|
81
|
+
}
|
|
82
|
+
return i;
|
|
83
|
+
}
|
|
84
|
+
coreValue(token, kind) {
|
|
85
|
+
return kind === "int" ? this.integer(token) : escape(token);
|
|
86
|
+
}
|
|
87
|
+
decodeAtoms(tokens, index, node, schema) {
|
|
88
|
+
let last = -1;
|
|
89
|
+
let i = index;
|
|
90
|
+
while (i < tokens.length) {
|
|
91
|
+
const token = tokens[i];
|
|
92
|
+
const position = schema.atomPosition(token);
|
|
93
|
+
if (position === undefined)
|
|
94
|
+
break;
|
|
95
|
+
if (position <= last) {
|
|
96
|
+
this.fail(`atom ${JSON.stringify(token)} is out of order or repeated`);
|
|
97
|
+
}
|
|
98
|
+
last = position;
|
|
99
|
+
const atom = schema.atoms[position];
|
|
100
|
+
const dash = token.indexOf("-");
|
|
101
|
+
const raw = dash === -1 ? "" : token.slice(dash + 1);
|
|
102
|
+
put(node, atom.parts, this.atomValue(raw, atom));
|
|
103
|
+
i += 1;
|
|
104
|
+
}
|
|
105
|
+
return i;
|
|
106
|
+
}
|
|
107
|
+
atomValue(raw, atom) {
|
|
108
|
+
if (atom.kind === "int")
|
|
109
|
+
return this.integer(raw);
|
|
110
|
+
if (atom.kind === "id")
|
|
111
|
+
return this.nested(raw);
|
|
112
|
+
if (atom.kind === "ids")
|
|
113
|
+
return splitTop(raw, ",").map((item) => this.nested(item));
|
|
114
|
+
return this.slotValue(raw, atom);
|
|
115
|
+
}
|
|
116
|
+
/** The value of a :string, :bool or list atom, or of a facet. */
|
|
117
|
+
slotValue(raw, slot) {
|
|
118
|
+
if (!slot.list)
|
|
119
|
+
return this.scalar(raw, slot);
|
|
120
|
+
return splitTop(raw, ",", true).map((item) => item === NULL ? null : this.scalar(item, slot));
|
|
121
|
+
}
|
|
122
|
+
scalar(raw, slot) {
|
|
123
|
+
return slot.element === "bool" ? this.boolean(raw) : escape(raw);
|
|
124
|
+
}
|
|
125
|
+
boolean(raw) {
|
|
126
|
+
if (raw === "true")
|
|
127
|
+
return true;
|
|
128
|
+
if (raw === "false")
|
|
129
|
+
return false;
|
|
130
|
+
this.fail(`${JSON.stringify(raw)} is not a boolean`);
|
|
131
|
+
}
|
|
132
|
+
integer(raw) {
|
|
133
|
+
const m = INTEGER.exec(raw);
|
|
134
|
+
if (m === null || m[0].length !== raw.length) {
|
|
135
|
+
this.fail(`${JSON.stringify(raw)} is not an integer`);
|
|
136
|
+
}
|
|
137
|
+
return Number(raw);
|
|
138
|
+
}
|
|
139
|
+
nested(raw) {
|
|
140
|
+
if (!raw.startsWith("(") || !raw.endsWith(")")) {
|
|
141
|
+
this.fail(`nested identifier ${JSON.stringify(raw)} needs brackets`);
|
|
142
|
+
}
|
|
143
|
+
return new _Decoder(raw.slice(1, -1)).toIdentifierHash();
|
|
144
|
+
}
|
|
145
|
+
decodeFacets(tokens, nodes, schemas) {
|
|
146
|
+
if (tokens.length === 0)
|
|
147
|
+
this.fail("empty facet group");
|
|
148
|
+
for (const token of tokens) {
|
|
149
|
+
const match = exactMatch(FACET, token);
|
|
150
|
+
if (match === null)
|
|
151
|
+
this.fail(`bad facet ${JSON.stringify(token)}`);
|
|
152
|
+
const index = Number(match[1]);
|
|
153
|
+
const schema = schemas[index];
|
|
154
|
+
if (schema === undefined)
|
|
155
|
+
this.fail(`facet ${JSON.stringify(token)} has no node`);
|
|
156
|
+
const facet = schema.facet(match[2]);
|
|
157
|
+
if (facet === undefined)
|
|
158
|
+
this.fail(`bad facet ${JSON.stringify(token)}`);
|
|
159
|
+
put(nodes[index], facet.parts, this.slotValue(match[3], facet));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
fail(message) {
|
|
163
|
+
throw new UrnParseError(`pubid URN ${JSON.stringify(this.body)}: ${message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes the pubid-scheme URN of an identifier hash — the TS mirror of
|
|
3
|
+
* Pubid::PubidUrn::Encoder (lib/pubid/pubid_urn/encoder.rb).
|
|
4
|
+
*
|
|
5
|
+
* urn:pubid:<flavor>:<node>[:<node>…][:f:<facet>…]
|
|
6
|
+
* <node> = <type>[:<core>…][:<prefix>-<value>…]
|
|
7
|
+
*
|
|
8
|
+
* The nodes go root first, along `base`: "ISO 9001:2015/Amd 1:2016" is
|
|
9
|
+
* "is:9001:year-2015:amd:1:year-2016", so every supplement of a document
|
|
10
|
+
* shares its slug prefix. A core value is positional; an absent one in
|
|
11
|
+
* the middle is "~", and absent ones at the end are dropped. An optional
|
|
12
|
+
* value is a prefixed atom, so it leaves no segment when it is absent.
|
|
13
|
+
*/
|
|
14
|
+
export declare class Encoder {
|
|
15
|
+
private readonly hash;
|
|
16
|
+
constructor(hash: Record<string, unknown>);
|
|
17
|
+
/** The body after "urn:pubid:". */
|
|
18
|
+
body(): string;
|
|
19
|
+
private encodeIdentifier;
|
|
20
|
+
/** The nodes from the root document to `hash` itself. */
|
|
21
|
+
private chain;
|
|
22
|
+
private token;
|
|
23
|
+
private coreSegments;
|
|
24
|
+
private atomSegments;
|
|
25
|
+
private atomValue;
|
|
26
|
+
/** The value of a :string, :bool or list atom, or of a facet. */
|
|
27
|
+
private slotValue;
|
|
28
|
+
private scalar;
|
|
29
|
+
private facetSegments;
|
|
30
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes the pubid-scheme URN of an identifier hash — the TS mirror of
|
|
3
|
+
* Pubid::PubidUrn::Encoder (lib/pubid/pubid_urn/encoder.rb).
|
|
4
|
+
*
|
|
5
|
+
* urn:pubid:<flavor>:<node>[:<node>…][:f:<facet>…]
|
|
6
|
+
* <node> = <type>[:<core>…][:<prefix>-<value>…]
|
|
7
|
+
*
|
|
8
|
+
* The nodes go root first, along `base`: "ISO 9001:2015/Amd 1:2016" is
|
|
9
|
+
* "is:9001:year-2015:amd:1:year-2016", so every supplement of a document
|
|
10
|
+
* shares its slug prefix. A core value is positional; an absent one in
|
|
11
|
+
* the middle is "~", and absent ones at the end are dropped. An optional
|
|
12
|
+
* value is a prefixed atom, so it leaves no segment when it is absent.
|
|
13
|
+
*/
|
|
14
|
+
import { typeSchemaOrThrow, structural } from "./index.js";
|
|
15
|
+
import { InvalidInputError } from "./errors.js";
|
|
16
|
+
import { encode as escapeEncode, force } from "./escape.js";
|
|
17
|
+
import { blank, deepDup, isNil, take } from "./paths.js";
|
|
18
|
+
import { Encoder as _Encoder } from "./encoder.js";
|
|
19
|
+
/** ruby's PubidUrn::NULL: the absent-slot marker. */
|
|
20
|
+
const NULL = "~";
|
|
21
|
+
export class Encoder {
|
|
22
|
+
hash;
|
|
23
|
+
constructor(hash) {
|
|
24
|
+
this.hash = hash;
|
|
25
|
+
}
|
|
26
|
+
/** The body after "urn:pubid:". */
|
|
27
|
+
body() {
|
|
28
|
+
const flavor = flavorSegmentOf(this.hash["_type"]);
|
|
29
|
+
return `${flavor}:${this.encodeIdentifier(this.hash, flavor)}`;
|
|
30
|
+
}
|
|
31
|
+
encodeIdentifier(hash, flavor) {
|
|
32
|
+
const segments = [];
|
|
33
|
+
const facets = [];
|
|
34
|
+
this.chain(hash).forEach((node, index) => {
|
|
35
|
+
const schema = typeSchemaOrThrow(node["_type"]);
|
|
36
|
+
const copy = deepDup(node);
|
|
37
|
+
delete copy["_type"];
|
|
38
|
+
segments.push(this.token(schema, flavor));
|
|
39
|
+
segments.push(...this.coreSegments(copy, schema));
|
|
40
|
+
segments.push(...this.atomSegments(copy, schema));
|
|
41
|
+
facets.push(...this.facetSegments(copy, schema, index));
|
|
42
|
+
checkPlaced(copy, schema);
|
|
43
|
+
});
|
|
44
|
+
if (facets.length > 0)
|
|
45
|
+
segments.push("f", ...facets);
|
|
46
|
+
return segments.join(":");
|
|
47
|
+
}
|
|
48
|
+
/** The nodes from the root document to `hash` itself. */
|
|
49
|
+
chain(hash) {
|
|
50
|
+
const nodes = [];
|
|
51
|
+
let node = hash;
|
|
52
|
+
while (node !== undefined && node !== null) {
|
|
53
|
+
if (typeof node !== "object" ||
|
|
54
|
+
Array.isArray(node) ||
|
|
55
|
+
typeof node["_type"] !== "string") {
|
|
56
|
+
throw new InvalidInputError(`a pubid URN base must be an identifier hash, got ${inspect(node)}`);
|
|
57
|
+
}
|
|
58
|
+
const copy = { ...node };
|
|
59
|
+
delete copy["base"];
|
|
60
|
+
nodes.unshift(copy);
|
|
61
|
+
node = node["base"];
|
|
62
|
+
}
|
|
63
|
+
return nodes;
|
|
64
|
+
}
|
|
65
|
+
token(schema, flavor) {
|
|
66
|
+
return schema.flavor === flavor ? schema.token : `${schema.flavor}.${schema.token}`;
|
|
67
|
+
}
|
|
68
|
+
coreSegments(node, schema) {
|
|
69
|
+
const values = schema.core.map((parts) => take(node, parts));
|
|
70
|
+
while (values.length > 0 && isNil(values[values.length - 1]))
|
|
71
|
+
values.pop();
|
|
72
|
+
return values.map((value, i) => {
|
|
73
|
+
if (isNil(value))
|
|
74
|
+
return NULL;
|
|
75
|
+
if (schema.coreKinds[i] === "int")
|
|
76
|
+
return coreInteger(value, schema, i);
|
|
77
|
+
return coreValue(value, schema);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
atomSegments(node, schema) {
|
|
81
|
+
const segments = [];
|
|
82
|
+
for (const atom of schema.atoms) {
|
|
83
|
+
const value = take(node, atom.parts);
|
|
84
|
+
if (isNil(value))
|
|
85
|
+
continue;
|
|
86
|
+
segments.push(`${atom.prefix}-${this.atomValue(value, atom, schema)}`);
|
|
87
|
+
}
|
|
88
|
+
return segments;
|
|
89
|
+
}
|
|
90
|
+
atomValue(value, atom, schema) {
|
|
91
|
+
if (atom.kind === "int")
|
|
92
|
+
return String(typed(value, "number", atom, schema));
|
|
93
|
+
if (atom.kind === "id") {
|
|
94
|
+
return nested(typed(value, "hash", atom, schema));
|
|
95
|
+
}
|
|
96
|
+
if (atom.kind === "ids") {
|
|
97
|
+
return typed(value, "array", atom, schema)
|
|
98
|
+
.map((hash) => nested(hash))
|
|
99
|
+
.join(",");
|
|
100
|
+
}
|
|
101
|
+
return this.slotValue(value, atom, schema);
|
|
102
|
+
}
|
|
103
|
+
/** The value of a :string, :bool or list atom, or of a facet. */
|
|
104
|
+
slotValue(value, slot, schema) {
|
|
105
|
+
if (!slot.list)
|
|
106
|
+
return this.scalar(value, slot, schema);
|
|
107
|
+
return typed(value, "array", slot, schema)
|
|
108
|
+
.map((item) => (isNil(item) ? NULL : this.scalar(item, slot, schema)))
|
|
109
|
+
.join(",");
|
|
110
|
+
}
|
|
111
|
+
scalar(value, slot, schema) {
|
|
112
|
+
if (slot.element === "bool") {
|
|
113
|
+
if (value !== true && value !== false) {
|
|
114
|
+
throw typeError(value, slot, schema);
|
|
115
|
+
}
|
|
116
|
+
return String(value);
|
|
117
|
+
}
|
|
118
|
+
return escapeEncode(typed(value, "string", slot, schema));
|
|
119
|
+
}
|
|
120
|
+
facetSegments(node, schema, index) {
|
|
121
|
+
const segments = [];
|
|
122
|
+
for (const facet of schema.facets) {
|
|
123
|
+
const value = take(node, facet.parts);
|
|
124
|
+
if (isNil(value))
|
|
125
|
+
continue;
|
|
126
|
+
segments.push(`${index}.${facet.name}-${this.slotValue(value, facet, schema)}`);
|
|
127
|
+
}
|
|
128
|
+
return segments;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function nested(hash) {
|
|
132
|
+
return `(${new _Encoder(hash).body()})`;
|
|
133
|
+
}
|
|
134
|
+
/** A decimal never has the shape of structure, so it needs no escape. */
|
|
135
|
+
function coreInteger(value, schema, index) {
|
|
136
|
+
if (typeof value === "number" && Number.isInteger(value))
|
|
137
|
+
return String(value);
|
|
138
|
+
throw new InvalidInputError(`${schema.type} ${schema.core[index]?.join(".")}: ${inspect(value)} is not int`);
|
|
139
|
+
}
|
|
140
|
+
/** A core value that has the shape of structure gets its first character
|
|
141
|
+
* escaped. */
|
|
142
|
+
function coreValue(value, schema) {
|
|
143
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
144
|
+
throw new InvalidInputError(`core value of ${schema.type} must be a non-empty String, got ${inspect(value)}`);
|
|
145
|
+
}
|
|
146
|
+
const encoded = escapeEncode(value);
|
|
147
|
+
if (!structural(encoded))
|
|
148
|
+
return encoded;
|
|
149
|
+
const chars = Array.from(value);
|
|
150
|
+
return force(chars[0]) + escapeEncode(chars.slice(1).join(""));
|
|
151
|
+
}
|
|
152
|
+
function typed(value, expected, slot, schema) {
|
|
153
|
+
let ok;
|
|
154
|
+
switch (expected) {
|
|
155
|
+
case "number":
|
|
156
|
+
ok = typeof value === "number" && Number.isInteger(value);
|
|
157
|
+
break;
|
|
158
|
+
case "string":
|
|
159
|
+
ok = typeof value === "string";
|
|
160
|
+
break;
|
|
161
|
+
case "hash":
|
|
162
|
+
ok = value !== null && typeof value === "object" && !Array.isArray(value);
|
|
163
|
+
break;
|
|
164
|
+
case "array":
|
|
165
|
+
ok = Array.isArray(value);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
if (ok)
|
|
169
|
+
return value;
|
|
170
|
+
throw typeError(value, slot, schema);
|
|
171
|
+
}
|
|
172
|
+
function typeError(value, slot, schema) {
|
|
173
|
+
return new InvalidInputError(`${schema.type} ${slot.path}: ${inspect(value)} is not ${slot.kind}`);
|
|
174
|
+
}
|
|
175
|
+
function checkPlaced(node, schema) {
|
|
176
|
+
const left = {};
|
|
177
|
+
for (const [key, value] of Object.entries(node)) {
|
|
178
|
+
if (!blank(value))
|
|
179
|
+
left[key] = value;
|
|
180
|
+
}
|
|
181
|
+
if (Object.keys(left).length > 0) {
|
|
182
|
+
throw new InvalidInputError(`the pubid URN schema of ${schema.type} does not place ${inspect(left)}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function inspect(value) {
|
|
186
|
+
if (typeof value === "string")
|
|
187
|
+
return JSON.stringify(value);
|
|
188
|
+
try {
|
|
189
|
+
return JSON.stringify(value) ?? String(value);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return String(value);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function flavorSegmentOf(type) {
|
|
196
|
+
return typeof type === "string" ? type.split(":")[1] : undefined;
|
|
197
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error classes of the pubid-scheme URN codec — the TS mirror of
|
|
3
|
+
* Pubid::Errors::UrnParseError and Pubid::Errors::InvalidInputError
|
|
4
|
+
* (lib/pubid/errors.rb). A decode failure is ALWAYS one of these two,
|
|
5
|
+
* never a parser-internal error (pubid#670 item 3).
|
|
6
|
+
*/
|
|
7
|
+
/** A malformed, unknown or non-canonical pubid-scheme URN (ruby:
|
|
8
|
+
* UrnParseError < StandardError). */
|
|
9
|
+
export declare class UrnParseError extends Error {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
/** A non-String or over-long input (ruby: InvalidInputError < ArgumentError). */
|
|
13
|
+
export declare class InvalidInputError extends Error {
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error classes of the pubid-scheme URN codec — the TS mirror of
|
|
3
|
+
* Pubid::Errors::UrnParseError and Pubid::Errors::InvalidInputError
|
|
4
|
+
* (lib/pubid/errors.rb). A decode failure is ALWAYS one of these two,
|
|
5
|
+
* never a parser-internal error (pubid#670 item 3).
|
|
6
|
+
*/
|
|
7
|
+
/** A malformed, unknown or non-canonical pubid-scheme URN (ruby:
|
|
8
|
+
* UrnParseError < StandardError). */
|
|
9
|
+
export class UrnParseError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "UrnParseError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** A non-String or over-long input (ruby: InvalidInputError < ArgumentError). */
|
|
16
|
+
export class InvalidInputError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "InvalidInputError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The value escape of the pubid-scheme URN — the TS mirror of
|
|
3
|
+
* Pubid::PubidUrn::Escape (lib/pubid/pubid_urn/escape.rb).
|
|
4
|
+
*
|
|
5
|
+
* The URN is also an HTTP path slug (relaton/relaton#441), so it cannot
|
|
6
|
+
* use "%": servers and proxies decode "%2F" before they route, or they
|
|
7
|
+
* refuse it. "~XX" (uppercase hex of one UTF-8 byte) has no meaning in a
|
|
8
|
+
* path, and "~" is an RFC 3986 unreserved character. Every byte outside
|
|
9
|
+
* SAFE is escaped, so ":" "," "(" ")" "~" never occur raw in a value and
|
|
10
|
+
* stay free for the structure of the URN.
|
|
11
|
+
*/
|
|
12
|
+
export declare function encode(value: string): string;
|
|
13
|
+
/** Escape every byte of `char`. The codec uses it on the first character
|
|
14
|
+
* of a core value that would otherwise read as structure ("amd" -> "~61md"). */
|
|
15
|
+
export declare function force(char: string): string;
|
|
16
|
+
export declare function decode(token: string): string;
|
|
17
|
+
/** A pattern whose ruby twin ends in \z: the match must cover the whole
|
|
18
|
+
* string (JS `$` alone would also match before one trailing "\n"). */
|
|
19
|
+
export declare function exactMatch(re: RegExp, token: string): RegExpExecArray | null;
|