@jarenjs/json 0.9.2
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/ARCHITECTURE.md +175 -0
- package/LICENSE +21 -0
- package/README.md +471 -0
- package/dist/types/basic.d.ts +32 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/jslt/dispatch.d.ts +11 -0
- package/dist/types/jslt/errors.d.ts +18 -0
- package/dist/types/jslt/index.d.ts +53 -0
- package/dist/types/jslt/stylesheet.d.ts +8 -0
- package/dist/types/jtlt/desugar.d.ts +19 -0
- package/dist/types/jtlt/errors.d.ts +18 -0
- package/dist/types/jtlt/index.d.ts +57 -0
- package/dist/types/jtlt/template.d.ts +8 -0
- package/dist/types/jtlt/writer.d.ts +6 -0
- package/dist/types/path.d.ts +235 -0
- package/dist/types/pointer.d.ts +114 -0
- package/dist/types/query/compile.d.ts +21 -0
- package/dist/types/query/errors.d.ts +18 -0
- package/dist/types/query/index.d.ts +70 -0
- package/dist/types/query/normalize.d.ts +68 -0
- package/dist/types/query/operators.d.ts +424 -0
- package/dist/types/query/runtime.d.ts +93 -0
- package/dist/types/segments.d.ts +62 -0
- package/dist/types/xquery/index.d.ts +19 -0
- package/dist/types/xquery/parse.d.ts +20 -0
- package/docs/JSLT-FORMAT.md +861 -0
- package/docs/JSLT-PRELUDE.md +159 -0
- package/docs/JTLT-FORMAT.md +659 -0
- package/docs/QUERY-FORMAT.md +1221 -0
- package/docs/XQUERY-FRONTEND.md +321 -0
- package/package.json +81 -0
- package/schemas/jaren-jslt.draft-07.schema.json +776 -0
- package/schemas/jaren-jslt.schema.json +776 -0
- package/schemas/jaren-query.draft-07.schema.json +613 -0
- package/schemas/jaren-query.schema.json +375 -0
- package/src/basic.js +300 -0
- package/src/index.js +4 -0
- package/src/jslt/dispatch.js +934 -0
- package/src/jslt/errors.js +34 -0
- package/src/jslt/index.js +121 -0
- package/src/jslt/stylesheet.js +234 -0
- package/src/jtlt/desugar.js +231 -0
- package/src/jtlt/errors.js +34 -0
- package/src/jtlt/index.js +155 -0
- package/src/jtlt/template.js +130 -0
- package/src/jtlt/writer.js +110 -0
- package/src/path.js +977 -0
- package/src/pointer.js +453 -0
- package/src/query/compile.js +817 -0
- package/src/query/errors.js +33 -0
- package/src/query/index.js +150 -0
- package/src/query/normalize.js +1047 -0
- package/src/query/operators.js +1253 -0
- package/src/query/runtime.js +233 -0
- package/src/segments.js +627 -0
- package/src/xquery/index.js +35 -0
- package/src/xquery/parse.js +1647 -0
package/src/pointer.js
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
//#region JSON Pointer (RFC 6901) + Relative JSON Pointer
|
|
2
|
+
// JSON Pointer: https://datatracker.ietf.org/doc/html/rfc6901
|
|
3
|
+
// Relative JSON Pointer:
|
|
4
|
+
// https://datatracker.ietf.org/doc/html/draft-luff-relative-json-pointer-00
|
|
5
|
+
//
|
|
6
|
+
// This module implements JSON Pointer as a two-stage compiler, mirroring
|
|
7
|
+
// the JSONPath engine in path.js:
|
|
8
|
+
//
|
|
9
|
+
// 1. `parseJSONPointer` / `parseRelativeJSONPointer` - strict, single-pass
|
|
10
|
+
// char-code parsers enforcing the full RFC 6901 / draft-luff grammar.
|
|
11
|
+
// 2. `compileJSONPointer` / `compileRelativeJSONPointer` /
|
|
12
|
+
// `compileDataRef` - compile the parsed form into specialized getter
|
|
13
|
+
// closures. All decisions (member name decoding, array index parsing,
|
|
14
|
+
// absolute-vs-relative dispatch) are taken at compile time; resolution
|
|
15
|
+
// allocates nothing and returns the shared `NOTHING` sentinel when the
|
|
16
|
+
// pointer does not address a location.
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
CC_SLASH,
|
|
20
|
+
CC_HASH,
|
|
21
|
+
CC_TILDE,
|
|
22
|
+
CC_0,
|
|
23
|
+
CC_1,
|
|
24
|
+
isDigitCode,
|
|
25
|
+
} from '@jarenjs/core/scan';
|
|
26
|
+
|
|
27
|
+
import { NOTHING } from './segments.js';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sentinel for the absence of a value, as distinct from the JSON value
|
|
31
|
+
* `null`. This is the same sentinel as `JSONPATH_NOTHING` in path.js, so
|
|
32
|
+
* pointer and path results can share checks.
|
|
33
|
+
*/
|
|
34
|
+
export const JSONPOINTER_NOTHING = NOTHING;
|
|
35
|
+
|
|
36
|
+
const hasOwn = Object.hasOwn;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when a (relative) JSON Pointer is not valid RFC 6901 /
|
|
40
|
+
* draft-luff-relative-json-pointer syntax.
|
|
41
|
+
*/
|
|
42
|
+
export class JSONPointerSyntaxError extends SyntaxError {
|
|
43
|
+
constructor(message, source, position) {
|
|
44
|
+
super(`Invalid JSON Pointer: ${message} at position ${position} in '${source}'`);
|
|
45
|
+
this.name = 'JSONPointerSyntaxError';
|
|
46
|
+
this.source = source;
|
|
47
|
+
this.position = position;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#region parsers
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Scan the segments of a pointer left to right, decoding `~0`/`~1`.
|
|
55
|
+
* `pos` must sit on the first `/` of the pointer part (or at the end of
|
|
56
|
+
* the source for an empty pointer). A `~` not followed by `0` or `1` is
|
|
57
|
+
* a syntax error (RFC 6901 section 3).
|
|
58
|
+
* @param {string} source - The pointer source text
|
|
59
|
+
* @param {number} pos - Index of the first '/' of the pointer part
|
|
60
|
+
* @returns {string[]} The decoded segments
|
|
61
|
+
*/
|
|
62
|
+
function scanSegments(source, pos) {
|
|
63
|
+
const len = source.length;
|
|
64
|
+
const segments = [];
|
|
65
|
+
while (pos < len) {
|
|
66
|
+
pos++; // consume '/'
|
|
67
|
+
const start = pos;
|
|
68
|
+
let decoded = null;
|
|
69
|
+
let chunk = start;
|
|
70
|
+
while (pos < len) {
|
|
71
|
+
const c = source.charCodeAt(pos);
|
|
72
|
+
if (c === CC_SLASH)
|
|
73
|
+
break;
|
|
74
|
+
if (c === CC_TILDE) {
|
|
75
|
+
const d = pos + 1 < len ? source.charCodeAt(pos + 1) : -1;
|
|
76
|
+
if (d === CC_0 || d === CC_1) {
|
|
77
|
+
decoded = (decoded === null ? '' : decoded)
|
|
78
|
+
+ source.slice(chunk, pos)
|
|
79
|
+
+ (d === CC_0 ? '~' : '/');
|
|
80
|
+
pos += 2;
|
|
81
|
+
chunk = pos;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
throw new JSONPointerSyntaxError("expected '0' or '1' after '~'", source, pos);
|
|
85
|
+
}
|
|
86
|
+
pos++;
|
|
87
|
+
}
|
|
88
|
+
segments.push(decoded === null
|
|
89
|
+
? source.slice(start, pos)
|
|
90
|
+
: decoded + source.slice(chunk, pos));
|
|
91
|
+
}
|
|
92
|
+
return segments;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Parse a JSON Pointer strictly per RFC 6901 into its decoded segments.
|
|
97
|
+
* @param {string} pointer - The JSON Pointer (e.g. `/store/book/0`)
|
|
98
|
+
* @returns {string[]} Array of decoded reference tokens
|
|
99
|
+
* @throws {JSONPointerSyntaxError} When the pointer violates the grammar
|
|
100
|
+
*/
|
|
101
|
+
export function parseJSONPointer(pointer) {
|
|
102
|
+
if (typeof pointer !== 'string')
|
|
103
|
+
throw new JSONPointerSyntaxError('pointer must be a string', String(pointer), 0);
|
|
104
|
+
if (pointer.length === 0)
|
|
105
|
+
return [];
|
|
106
|
+
if (pointer.charCodeAt(0) !== CC_SLASH)
|
|
107
|
+
throw new JSONPointerSyntaxError("a non-empty pointer must start with '/'", pointer, 0);
|
|
108
|
+
return scanSegments(pointer, 0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A parsed Relative JSON Pointer (draft-luff-relative-json-pointer).
|
|
113
|
+
* @typedef {Object} RelativeJsonPointer
|
|
114
|
+
* @property {number} levels - Number of levels to ascend from the current location
|
|
115
|
+
* @property {boolean} hash - True for the `#` form, which addresses the member name or array index itself
|
|
116
|
+
* @property {string[]} segments - Decoded reference tokens applied after ascending
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A compiled JSON Pointer: returns the value addressed in `root`, or the
|
|
121
|
+
* `JSONPOINTER_NOTHING` sentinel when the pointer does not address a location.
|
|
122
|
+
* @typedef {(root: any) => any} JsonPointerGetter
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A compiled Relative JSON Pointer / data reference: resolves against the
|
|
127
|
+
* RFC 6901 location `dataPath` inside `dataRoot`, returning the addressed
|
|
128
|
+
* value or the `JSONPOINTER_NOTHING` sentinel.
|
|
129
|
+
* @typedef {(dataRoot: any, dataPath: string) => any} RelativeJsonPointerResolver
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Parse a Relative JSON Pointer strictly per
|
|
134
|
+
* draft-luff-relative-json-pointer: a non-negative integer without
|
|
135
|
+
* leading zeros, followed by `#` or a JSON Pointer.
|
|
136
|
+
* @param {string} pointer - The relative pointer (e.g. `1/sibling`, `0#`)
|
|
137
|
+
* @returns {RelativeJsonPointer} The parsed relative pointer
|
|
138
|
+
* @throws {JSONPointerSyntaxError} When the pointer violates the grammar
|
|
139
|
+
*/
|
|
140
|
+
export function parseRelativeJSONPointer(pointer) {
|
|
141
|
+
if (typeof pointer !== 'string')
|
|
142
|
+
throw new JSONPointerSyntaxError('pointer must be a string', String(pointer), 0);
|
|
143
|
+
const len = pointer.length;
|
|
144
|
+
if (len === 0)
|
|
145
|
+
throw new JSONPointerSyntaxError('empty relative pointer', pointer, 0);
|
|
146
|
+
const first = pointer.charCodeAt(0);
|
|
147
|
+
if (!isDigitCode(first))
|
|
148
|
+
throw new JSONPointerSyntaxError('expected a non-negative integer', pointer, 0);
|
|
149
|
+
let pos = 1;
|
|
150
|
+
if (first === CC_0) {
|
|
151
|
+
if (pos < len && isDigitCode(pointer.charCodeAt(pos)))
|
|
152
|
+
throw new JSONPointerSyntaxError('leading zeros are not allowed', pointer, 0);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
while (pos < len && isDigitCode(pointer.charCodeAt(pos)))
|
|
156
|
+
pos++;
|
|
157
|
+
}
|
|
158
|
+
const levels = Number(pointer.slice(0, pos));
|
|
159
|
+
if (!Number.isSafeInteger(levels))
|
|
160
|
+
throw new JSONPointerSyntaxError('level count out of range', pointer, 0);
|
|
161
|
+
if (pos === len)
|
|
162
|
+
return { levels, hash: false, segments: [] };
|
|
163
|
+
const c = pointer.charCodeAt(pos);
|
|
164
|
+
if (c === CC_HASH) {
|
|
165
|
+
if (pos + 1 !== len)
|
|
166
|
+
throw new JSONPointerSyntaxError("'#' must end the pointer", pointer, pos + 1);
|
|
167
|
+
return { levels, hash: true, segments: [] };
|
|
168
|
+
}
|
|
169
|
+
if (c !== CC_SLASH)
|
|
170
|
+
throw new JSONPointerSyntaxError("expected '#' or a JSON Pointer after the level count", pointer, pos);
|
|
171
|
+
return { levels, hash: false, segments: scanSegments(pointer, pos) };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
|
|
176
|
+
//#region compiler
|
|
177
|
+
|
|
178
|
+
// Array indexes are bounded by the maximum array length (2^32 - 1), so a
|
|
179
|
+
// valid index has at most 10 digits and is strictly below 2^32 - 1.
|
|
180
|
+
const MAX_ARRAY_INDEX = 4294967294;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Scan `source[start..end)` as an RFC 6901 array index: `0`, or a digit
|
|
184
|
+
* sequence without leading zeros. Returns -1 when the range is not a
|
|
185
|
+
* valid index (`-` is never a valid read index).
|
|
186
|
+
*/
|
|
187
|
+
function scanArrayIndex(source, start, end) {
|
|
188
|
+
const digits = end - start;
|
|
189
|
+
if (digits === 0 || digits > 10)
|
|
190
|
+
return -1;
|
|
191
|
+
const first = source.charCodeAt(start);
|
|
192
|
+
if (!isDigitCode(first))
|
|
193
|
+
return -1;
|
|
194
|
+
if (first === CC_0)
|
|
195
|
+
return digits === 1 ? 0 : -1;
|
|
196
|
+
let index = first - CC_0;
|
|
197
|
+
for (let i = start + 1; i < end; i++) {
|
|
198
|
+
const c = source.charCodeAt(i);
|
|
199
|
+
if (!isDigitCode(c))
|
|
200
|
+
return -1;
|
|
201
|
+
index = index * 10 + (c - CC_0);
|
|
202
|
+
}
|
|
203
|
+
return index <= MAX_ARRAY_INDEX ? index : -1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* One pointer hop: an array is addressed by the pre-parsed index, an
|
|
208
|
+
* object by the pre-decoded member name (own properties only), anything
|
|
209
|
+
* else has no addressable children. RFC 6901 requires a segment like "2"
|
|
210
|
+
* to address both `{"2": x}` members and array element 2 - one segment,
|
|
211
|
+
* two pre-computed forms.
|
|
212
|
+
*/
|
|
213
|
+
function hop(v, name, index) {
|
|
214
|
+
if (typeof v !== 'object' || v === null)
|
|
215
|
+
return NOTHING;
|
|
216
|
+
if (Array.isArray(v))
|
|
217
|
+
return (index >= 0 && index < v.length) ? v[index] : NOTHING;
|
|
218
|
+
return hasOwn(v, name) ? v[name] : NOTHING;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function getRoot(root) {
|
|
222
|
+
return root;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Compile decoded segments into a specialized getter, unrolled by
|
|
227
|
+
* segment count like the validator's composition table.
|
|
228
|
+
* @param {string[]} segments - Decoded reference tokens
|
|
229
|
+
* @returns {(root: any) => any} getter returning the value or NOTHING
|
|
230
|
+
*/
|
|
231
|
+
function compileSegmentsGetter(segments) {
|
|
232
|
+
const slen = segments.length;
|
|
233
|
+
if (slen === 0)
|
|
234
|
+
return getRoot;
|
|
235
|
+
const name0 = segments[0];
|
|
236
|
+
const index0 = scanArrayIndex(name0, 0, name0.length);
|
|
237
|
+
if (slen === 1) {
|
|
238
|
+
return function pointerGetter1(root) {
|
|
239
|
+
return hop(root, name0, index0);
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const name1 = segments[1];
|
|
243
|
+
const index1 = scanArrayIndex(name1, 0, name1.length);
|
|
244
|
+
if (slen === 2) {
|
|
245
|
+
return function pointerGetter2(root) {
|
|
246
|
+
const v = hop(root, name0, index0);
|
|
247
|
+
return v === NOTHING ? NOTHING : hop(v, name1, index1);
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const names = segments;
|
|
251
|
+
const indexes = new Array(slen);
|
|
252
|
+
for (let i = 0; i < slen; i++)
|
|
253
|
+
indexes[i] = scanArrayIndex(segments[i], 0, segments[i].length);
|
|
254
|
+
return function pointerGetterN(root) {
|
|
255
|
+
let v = root;
|
|
256
|
+
for (let i = 0; i < slen; i++) {
|
|
257
|
+
v = hop(v, names[i], indexes[i]);
|
|
258
|
+
if (v === NOTHING)
|
|
259
|
+
return NOTHING;
|
|
260
|
+
}
|
|
261
|
+
return v;
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Compile a JSON Pointer (RFC 6901) into a reusable getter.
|
|
267
|
+
*
|
|
268
|
+
* All decisions are taken at compile time: member names are pre-decoded,
|
|
269
|
+
* array indexes pre-parsed, and the getter is specialized by segment
|
|
270
|
+
* count. Resolution allocates nothing.
|
|
271
|
+
*
|
|
272
|
+
* @param {string} pointer - The JSON Pointer (e.g. `/store/book/0`)
|
|
273
|
+
* @returns {JsonPointerGetter} getter returning the addressed value, or
|
|
274
|
+
* `JSONPOINTER_NOTHING` when the pointer does not address a location
|
|
275
|
+
* @throws {JSONPointerSyntaxError} When the pointer is not valid RFC 6901
|
|
276
|
+
* @example
|
|
277
|
+
* const get = compileJSONPointer('/limits/min');
|
|
278
|
+
* get({ limits: { min: 2 } }); // 2
|
|
279
|
+
* get({}); // JSONPOINTER_NOTHING
|
|
280
|
+
*/
|
|
281
|
+
export function compileJSONPointer(pointer) {
|
|
282
|
+
return compileSegmentsGetter(parseJSONPointer(pointer));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Trim `levels` segments off the end of an RFC 6901 location path by
|
|
287
|
+
* scanning backwards for the N-th '/'. Returns the exclusive end index
|
|
288
|
+
* of the trimmed prefix, or -1 when `levels` exceeds the depth.
|
|
289
|
+
*/
|
|
290
|
+
function trimLevels(dataPath, levels) {
|
|
291
|
+
let end = dataPath.length;
|
|
292
|
+
for (let i = 0; i < levels; i++) {
|
|
293
|
+
if (end === 0)
|
|
294
|
+
return -1;
|
|
295
|
+
end = dataPath.lastIndexOf('/', end - 1);
|
|
296
|
+
if (end < 0)
|
|
297
|
+
return -1;
|
|
298
|
+
}
|
|
299
|
+
return end;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Decode `source[start..end)` where `tilde` is the position of the first
|
|
304
|
+
* `~`. Lax decode: invalid escapes are kept literally (location paths are
|
|
305
|
+
* machine-generated; this mirrors the historical decode).
|
|
306
|
+
*/
|
|
307
|
+
function decodeSegmentRange(source, start, end, tilde) {
|
|
308
|
+
let out = source.slice(start, tilde);
|
|
309
|
+
let pos = tilde;
|
|
310
|
+
let chunk = tilde;
|
|
311
|
+
while (pos < end) {
|
|
312
|
+
if (source.charCodeAt(pos) === CC_TILDE) {
|
|
313
|
+
const d = pos + 1 < end ? source.charCodeAt(pos + 1) : -1;
|
|
314
|
+
if (d === CC_0 || d === CC_1) {
|
|
315
|
+
out += source.slice(chunk, pos) + (d === CC_0 ? '~' : '/');
|
|
316
|
+
pos += 2;
|
|
317
|
+
chunk = pos;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
pos++;
|
|
322
|
+
}
|
|
323
|
+
return out + source.slice(chunk, end);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* The last segment of `dataPath.slice(0, end)`, decoded lazily: the
|
|
328
|
+
* common escape-free case allocates nothing beyond the result slice.
|
|
329
|
+
* At the root (`end === 0`) the name of the location is `''`.
|
|
330
|
+
*/
|
|
331
|
+
function lastSegmentOf(dataPath, end) {
|
|
332
|
+
if (end === 0)
|
|
333
|
+
return '';
|
|
334
|
+
const start = dataPath.lastIndexOf('/', end - 1) + 1;
|
|
335
|
+
for (let i = start; i < end; i++) {
|
|
336
|
+
if (dataPath.charCodeAt(i) === CC_TILDE)
|
|
337
|
+
return decodeSegmentRange(dataPath, start, end, i);
|
|
338
|
+
}
|
|
339
|
+
return dataPath.slice(start, end);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Walk `root` along the location path prefix `path.slice(0, end)`.
|
|
344
|
+
* Segments are decoded lazily per hop (escape-free segments are sliced
|
|
345
|
+
* directly; array indexes are scanned in place without allocating).
|
|
346
|
+
*/
|
|
347
|
+
function walkPointerPrefix(root, path, end) {
|
|
348
|
+
let v = root;
|
|
349
|
+
let pos = 0;
|
|
350
|
+
while (pos < end) {
|
|
351
|
+
pos++; // consume '/'
|
|
352
|
+
const start = pos;
|
|
353
|
+
let tilde = -1;
|
|
354
|
+
while (pos < end) {
|
|
355
|
+
const c = path.charCodeAt(pos);
|
|
356
|
+
if (c === CC_SLASH)
|
|
357
|
+
break;
|
|
358
|
+
if (c === CC_TILDE && tilde < 0)
|
|
359
|
+
tilde = pos;
|
|
360
|
+
pos++;
|
|
361
|
+
}
|
|
362
|
+
if (typeof v !== 'object' || v === null)
|
|
363
|
+
return NOTHING;
|
|
364
|
+
if (Array.isArray(v)) {
|
|
365
|
+
const index = scanArrayIndex(path, start, pos);
|
|
366
|
+
if (index < 0 || index >= v.length)
|
|
367
|
+
return NOTHING;
|
|
368
|
+
v = v[index];
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
const name = tilde < 0 ? path.slice(start, pos) : decodeSegmentRange(path, start, pos, tilde);
|
|
372
|
+
if (!hasOwn(v, name))
|
|
373
|
+
return NOTHING;
|
|
374
|
+
v = v[name];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return v;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Compile a Relative JSON Pointer into a reusable resolver.
|
|
382
|
+
*
|
|
383
|
+
* The relative part (level count, `#` form, trailing segments) compiles
|
|
384
|
+
* once; per call only `dataPath` - the current location in `dataRoot` as
|
|
385
|
+
* an RFC 6901 pointer - varies. The `#` form resolves to the member name
|
|
386
|
+
* or array index of the location **as a string** (`''` at the root),
|
|
387
|
+
* matching the historical behavior relied on by the validator's `$data`
|
|
388
|
+
* keyword.
|
|
389
|
+
*
|
|
390
|
+
* @param {string} pointer - The relative pointer (e.g. `1/sibling`, `0#`)
|
|
391
|
+
* @returns {RelativeJsonPointerResolver} resolver returning
|
|
392
|
+
* the addressed value, or `JSONPOINTER_NOTHING`
|
|
393
|
+
* @throws {JSONPointerSyntaxError} When the pointer is not valid
|
|
394
|
+
* @example
|
|
395
|
+
* const resolve = compileRelativeJSONPointer('1/limits');
|
|
396
|
+
* resolve({ limits: { min: 2 } , value: 5 }, '/value'); // { min: 2 }
|
|
397
|
+
*/
|
|
398
|
+
export function compileRelativeJSONPointer(pointer) {
|
|
399
|
+
const { levels, hash, segments } = parseRelativeJSONPointer(pointer);
|
|
400
|
+
if (hash) {
|
|
401
|
+
return function relativeHashResolver(dataRoot, dataPath) {
|
|
402
|
+
if (typeof dataPath !== 'string')
|
|
403
|
+
dataPath = '';
|
|
404
|
+
else if (dataPath.length !== 0 && dataPath.charCodeAt(0) !== CC_SLASH)
|
|
405
|
+
return NOTHING;
|
|
406
|
+
const end = trimLevels(dataPath, levels);
|
|
407
|
+
if (end < 0)
|
|
408
|
+
return NOTHING;
|
|
409
|
+
return lastSegmentOf(dataPath, end);
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
const getter = compileSegmentsGetter(segments);
|
|
413
|
+
return function relativeResolver(dataRoot, dataPath) {
|
|
414
|
+
if (typeof dataPath !== 'string')
|
|
415
|
+
dataPath = '';
|
|
416
|
+
else if (dataPath.length !== 0 && dataPath.charCodeAt(0) !== CC_SLASH)
|
|
417
|
+
return NOTHING;
|
|
418
|
+
const end = trimLevels(dataPath, levels);
|
|
419
|
+
if (end < 0)
|
|
420
|
+
return NOTHING;
|
|
421
|
+
const base = walkPointerPrefix(dataRoot, dataPath, end);
|
|
422
|
+
return base === NOTHING ? NOTHING : getter(base);
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Compile a data reference - the accepted forms of the validator's
|
|
428
|
+
* `data`/`$data` keywords - into a reusable resolver. The dispatch is
|
|
429
|
+
* decided once at compile time: a leading digit is a Relative JSON
|
|
430
|
+
* Pointer, a leading `/` an absolute JSON Pointer, and `''` the root.
|
|
431
|
+
*
|
|
432
|
+
* @param {string} ref - The reference string
|
|
433
|
+
* @returns {RelativeJsonPointerResolver} resolver returning
|
|
434
|
+
* the addressed value, or `JSONPOINTER_NOTHING`
|
|
435
|
+
* @throws {JSONPointerSyntaxError} When the reference is none of the
|
|
436
|
+
* accepted forms
|
|
437
|
+
*/
|
|
438
|
+
export function compileDataRef(ref) {
|
|
439
|
+
if (typeof ref !== 'string')
|
|
440
|
+
throw new JSONPointerSyntaxError('a data reference must be a string', String(ref), 0);
|
|
441
|
+
if (ref.length === 0)
|
|
442
|
+
return getRoot;
|
|
443
|
+
const c = ref.charCodeAt(0);
|
|
444
|
+
if (isDigitCode(c))
|
|
445
|
+
return compileRelativeJSONPointer(ref);
|
|
446
|
+
if (c === CC_SLASH)
|
|
447
|
+
return compileSegmentsGetter(scanSegments(ref, 0));
|
|
448
|
+
throw new JSONPointerSyntaxError('a data reference must be empty, a JSON Pointer or a Relative JSON Pointer', ref, 0);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
//#endregion
|
|
452
|
+
|
|
453
|
+
//#endregion
|