@graphty/graph-io 0.3.18 → 0.3.19
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/README.md +37 -2
- package/dist/chunks/{escape-D-gZWO26.js → escape-CWExcecC.js} +3 -3
- package/dist/chunks/{escape-D-gZWO26.js.map → escape-CWExcecC.js.map} +1 -1
- package/dist/chunks/exporter-CbMZyVt-.js +735 -0
- package/dist/chunks/exporter-CbMZyVt-.js.map +1 -0
- package/dist/chunks/importer-BAP4PrxR.js +2194 -0
- package/dist/chunks/importer-BAP4PrxR.js.map +1 -0
- package/dist/chunks/{importer-Br_QeAeE.js → importer-BNFuV1-K.js} +5 -4
- package/dist/chunks/{importer-Br_QeAeE.js.map → importer-BNFuV1-K.js.map} +1 -1
- package/dist/chunks/{importer-d0uQxFp6.js → importer-BVGtU1NA.js} +5 -4
- package/dist/chunks/{importer-d0uQxFp6.js.map → importer-BVGtU1NA.js.map} +1 -1
- package/dist/chunks/{importer-DHagxvDD.js → importer-BW-Ft2ps.js} +5 -4
- package/dist/chunks/{importer-DHagxvDD.js.map → importer-BW-Ft2ps.js.map} +1 -1
- package/dist/chunks/{importer-Du5crN9l.js → importer-Bm1_5vPS.js} +275 -82
- package/dist/chunks/importer-Bm1_5vPS.js.map +1 -0
- package/dist/chunks/{importer-aNJfe0qu.js → importer-ByPGO-09.js} +2 -2
- package/dist/chunks/{importer-aNJfe0qu.js.map → importer-ByPGO-09.js.map} +1 -1
- package/dist/chunks/importer-DOepkgnG.js +1713 -0
- package/dist/chunks/importer-DOepkgnG.js.map +1 -0
- package/dist/chunks/json-elements-CZY1wiZh.js +779 -0
- package/dist/chunks/json-elements-CZY1wiZh.js.map +1 -0
- package/dist/chunks/{records-Bk9jgodz.js → records-BzNicMsf.js} +2 -2
- package/dist/chunks/{records-Bk9jgodz.js.map → records-BzNicMsf.js.map} +1 -1
- package/dist/chunks/{report-BOk0p5y8.js → report-BcWboivV.js} +95 -95
- package/dist/chunks/{report-BOk0p5y8.js.map → report-BcWboivV.js.map} +1 -1
- package/dist/chunks/weights-CwISIpCP.js +176 -0
- package/dist/chunks/weights-CwISIpCP.js.map +1 -0
- package/dist/chunks/{writer-GAdltGmC.js → writer-DQiKgQJc.js} +8 -179
- package/dist/chunks/writer-DQiKgQJc.js.map +1 -0
- package/dist/csv.js +5 -4
- package/dist/csv.js.map +1 -1
- package/dist/cx.d.ts +1 -0
- package/dist/cx.js +6 -0
- package/dist/cx.js.map +1 -0
- package/dist/cx2.d.ts +1 -0
- package/dist/cx2.js +10 -0
- package/dist/cx2.js.map +1 -0
- package/dist/dot.js +1 -1
- package/dist/gexf.js +15 -4
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +5 -4
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +145 -389
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +1 -1
- package/dist/json.js +1 -1
- package/dist/neo4j.js +5 -4
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/json-elements.d.ts +286 -0
- package/dist/src/common/json-elements.d.ts.map +1 -0
- package/dist/src/common/json-elements.js +926 -0
- package/dist/src/common/json-elements.js.map +1 -0
- package/dist/src/formats/cx/importer.d.ts +133 -0
- package/dist/src/formats/cx/importer.d.ts.map +1 -0
- package/dist/src/formats/cx/importer.js +2220 -0
- package/dist/src/formats/cx/importer.js.map +1 -0
- package/dist/src/formats/cx/index.d.ts +7 -0
- package/dist/src/formats/cx/index.d.ts.map +1 -0
- package/dist/src/formats/cx/index.js +7 -0
- package/dist/src/formats/cx/index.js.map +1 -0
- package/dist/src/formats/cx2/exporter.d.ts +59 -0
- package/dist/src/formats/cx2/exporter.d.ts.map +1 -0
- package/dist/src/formats/cx2/exporter.js +864 -0
- package/dist/src/formats/cx2/exporter.js.map +1 -0
- package/dist/src/formats/cx2/importer.d.ts +169 -0
- package/dist/src/formats/cx2/importer.d.ts.map +1 -0
- package/dist/src/formats/cx2/importer.js +1652 -0
- package/dist/src/formats/cx2/importer.js.map +1 -0
- package/dist/src/formats/cx2/index.d.ts +7 -0
- package/dist/src/formats/cx2/index.d.ts.map +1 -0
- package/dist/src/formats/cx2/index.js +7 -0
- package/dist/src/formats/cx2/index.js.map +1 -0
- package/dist/src/formats/gexf/exporter.d.ts +2 -0
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +6 -0
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts +2 -4
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +2 -4
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gexf/index.d.ts +1 -1
- package/dist/src/formats/gexf/index.js +2 -2
- package/dist/src/formats/gexf/index.js.map +1 -1
- package/dist/src/formats/gml/exporter.js +1 -1
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +6 -104
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +5 -0
- package/dist/src/registry.js.map +1 -1
- package/dist/src/sniff.d.ts +1 -1
- package/dist/src/sniff.d.ts.map +1 -1
- package/dist/src/sniff.js +2 -0
- package/dist/src/sniff.js.map +1 -1
- package/package.json +11 -1
- package/src/common/json-elements.ts +1147 -0
- package/src/formats/cx/importer.ts +2733 -0
- package/src/formats/cx/index.ts +7 -0
- package/src/formats/cx2/exporter.ts +1036 -0
- package/src/formats/cx2/importer.ts +2187 -0
- package/src/formats/cx2/index.ts +7 -0
- package/src/formats/gexf/exporter.ts +11 -0
- package/src/formats/gexf/importer.ts +2 -2
- package/src/formats/gexf/index.ts +1 -1
- package/src/formats/gml/exporter.ts +1 -1
- package/src/formats/json/importer.ts +6 -105
- package/src/index.ts +10 -0
- package/src/registry.ts +5 -0
- package/src/sniff.ts +14 -1
- package/dist/chunks/importer-Du5crN9l.js.map +0 -1
- package/dist/chunks/writer-GAdltGmC.js.map +0 -1
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
|
|
2
|
+
import { B as BAD_ASPECT_BLOCK_CODE, a as ASPECT_ORDER_CODE, a3 as STATUS_FAILED_CODE, a4 as STATUS_WARNING_CODE, g as COUNT_MISMATCH_CODE, aE as uniqueColumnName, C as COLUMN_RENAMED_CODE, ar as declareResolved } from "./report-BcWboivV.js";
|
|
3
|
+
const MAYBE_UNSAFE_INTEGER = new RegExp("(?<![0-9.])[0-9]{16}");
|
|
4
|
+
const SENTINEL = `${String.fromCharCode(0)}graph-io:`;
|
|
5
|
+
const EXACT_SENTINEL = `${SENTINEL}exact:`;
|
|
6
|
+
const NONSTANDARD_TOKENS = [
|
|
7
|
+
["-Infinity", -Infinity],
|
|
8
|
+
["Infinity", Infinity],
|
|
9
|
+
["NaN", NaN]
|
|
10
|
+
];
|
|
11
|
+
const INTEGER_LITERAL = /^-?(0|[1-9][0-9]*)$/;
|
|
12
|
+
function rewriteNumbers(text, options = {}) {
|
|
13
|
+
const nonstandard = options.nonstandard ?? true;
|
|
14
|
+
const exactSentinel = options.exactSentinel ?? false;
|
|
15
|
+
const parts = [];
|
|
16
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
17
|
+
const bigIntegers = [];
|
|
18
|
+
const arrays = [];
|
|
19
|
+
let expectValue = true;
|
|
20
|
+
let copied = 0;
|
|
21
|
+
let i = 0;
|
|
22
|
+
const n = text.length;
|
|
23
|
+
while (i < n) {
|
|
24
|
+
const ch = text[i];
|
|
25
|
+
if (ch === '"') {
|
|
26
|
+
i++;
|
|
27
|
+
while (i < n && text[i] !== '"') {
|
|
28
|
+
i += text[i] === "\\" ? 2 : 1;
|
|
29
|
+
}
|
|
30
|
+
i++;
|
|
31
|
+
expectValue = false;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (ch === "{" || ch === "[") {
|
|
35
|
+
arrays.push(ch === "[");
|
|
36
|
+
expectValue = ch === "[";
|
|
37
|
+
} else if (ch === "}" || ch === "]") {
|
|
38
|
+
arrays.pop();
|
|
39
|
+
expectValue = false;
|
|
40
|
+
} else if (ch === ":") {
|
|
41
|
+
expectValue = true;
|
|
42
|
+
} else if (ch === ",") {
|
|
43
|
+
expectValue = arrays.length > 0 && arrays[arrays.length - 1];
|
|
44
|
+
} else if (expectValue && ch !== " " && ch !== " " && ch !== "\n" && ch !== "\r") {
|
|
45
|
+
expectValue = false;
|
|
46
|
+
const token = nonstandard ? NONSTANDARD_TOKENS.find(([word]) => text.startsWith(word, i)) : void 0;
|
|
47
|
+
let end = i;
|
|
48
|
+
let replacement = null;
|
|
49
|
+
if (token !== void 0) {
|
|
50
|
+
end = i + token[0].length;
|
|
51
|
+
tokens.add(token[0]);
|
|
52
|
+
replacement = JSON.stringify(`${SENTINEL}${token[0]}`);
|
|
53
|
+
} else if (ch === "-" || ch >= "0" && ch <= "9") {
|
|
54
|
+
end = i + 1;
|
|
55
|
+
while (end < n && "0123456789+-.eE".includes(text[end])) {
|
|
56
|
+
end++;
|
|
57
|
+
}
|
|
58
|
+
const literal = text.slice(i, end);
|
|
59
|
+
if (INTEGER_LITERAL.test(literal) && !Number.isSafeInteger(Number(literal))) {
|
|
60
|
+
bigIntegers.push(literal);
|
|
61
|
+
replacement = exactSentinel ? JSON.stringify(`${EXACT_SENTINEL}${literal}`) : `"${literal}"`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (replacement !== null) {
|
|
65
|
+
parts.push(text.slice(copied, i), replacement);
|
|
66
|
+
copied = end;
|
|
67
|
+
}
|
|
68
|
+
i = Math.max(end, i + 1);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
i++;
|
|
72
|
+
}
|
|
73
|
+
parts.push(text.slice(copied));
|
|
74
|
+
return { text: parts.join(""), tokens, bigIntegers };
|
|
75
|
+
}
|
|
76
|
+
function reviveNonstandard(_key, value) {
|
|
77
|
+
if (typeof value === "string" && value.startsWith(SENTINEL)) {
|
|
78
|
+
const found = NONSTANDARD_TOKENS.find(([word]) => word === value.slice(SENTINEL.length));
|
|
79
|
+
return found === void 0 ? value : found[1];
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
class ExactInteger {
|
|
84
|
+
/**
|
|
85
|
+
* Wrap the digits of an integer literal.
|
|
86
|
+
* @param digits - the literal
|
|
87
|
+
*/
|
|
88
|
+
constructor(digits) {
|
|
89
|
+
this.digits = digits;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The nearest number (JSON.stringify writes it as such).
|
|
93
|
+
* @returns the number
|
|
94
|
+
*/
|
|
95
|
+
toJSON() {
|
|
96
|
+
return Number(this.digits);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function reviveExact(_key, value) {
|
|
100
|
+
if (typeof value === "string" && value.startsWith(EXACT_SENTINEL)) {
|
|
101
|
+
return new ExactInteger(value.slice(EXACT_SENTINEL.length));
|
|
102
|
+
}
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
function parseExact(text) {
|
|
106
|
+
if (!MAYBE_UNSAFE_INTEGER.test(text)) {
|
|
107
|
+
return { value: JSON.parse(text), exact: false };
|
|
108
|
+
}
|
|
109
|
+
const scan = rewriteNumbers(text, { nonstandard: false, exactSentinel: true });
|
|
110
|
+
if (scan.bigIntegers.length === 0) {
|
|
111
|
+
return { value: JSON.parse(text), exact: false };
|
|
112
|
+
}
|
|
113
|
+
return { value: JSON.parse(scan.text, reviveExact), exact: true };
|
|
114
|
+
}
|
|
115
|
+
function plainJson(value, onPrecision) {
|
|
116
|
+
if (value instanceof ExactInteger) {
|
|
117
|
+
onPrecision?.(value.digits);
|
|
118
|
+
return Number(value.digits);
|
|
119
|
+
}
|
|
120
|
+
if (Array.isArray(value)) {
|
|
121
|
+
return value.map((item) => plainJson(item, onPrecision));
|
|
122
|
+
}
|
|
123
|
+
if (typeof value === "object" && value !== null) {
|
|
124
|
+
const out = {};
|
|
125
|
+
for (const [key, item] of Object.entries(value)) {
|
|
126
|
+
out[key] = plainJson(item, onPrecision);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
class JsonScanError extends Error {
|
|
133
|
+
/**
|
|
134
|
+
* Create the error.
|
|
135
|
+
* @param message - what is wrong
|
|
136
|
+
* @param line - the 1-based line
|
|
137
|
+
* @param empty - whether the input is empty
|
|
138
|
+
*/
|
|
139
|
+
constructor(message, line, empty = false) {
|
|
140
|
+
super(message);
|
|
141
|
+
this.name = "JsonScanError";
|
|
142
|
+
this.line = line;
|
|
143
|
+
this.empty = empty;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const isSpace = (c) => c === 32 || c === 10 || c === 13 || c === 9;
|
|
147
|
+
const endsScalar = (c) => isSpace(c) || c === 44 || c === 93 || c === 125 || c === 58;
|
|
148
|
+
const QUOTE_OR_BACKSLASH = /["\\]/g;
|
|
149
|
+
const MAX_ELEMENT_DEPTH = 200;
|
|
150
|
+
class ChunkCursor {
|
|
151
|
+
/**
|
|
152
|
+
* Wrap text chunks.
|
|
153
|
+
* @param chunks - the decoded text
|
|
154
|
+
*/
|
|
155
|
+
constructor(chunks) {
|
|
156
|
+
this.buf = "";
|
|
157
|
+
this.pos = 0;
|
|
158
|
+
this.linesBefore = 1;
|
|
159
|
+
this.parts = null;
|
|
160
|
+
this.captureStart = 0;
|
|
161
|
+
this.deepest = 0;
|
|
162
|
+
this.iterator = chunks[Symbol.asyncIterator]();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Read the next chunk once the current one is consumed.
|
|
166
|
+
* @returns false at the end of the input
|
|
167
|
+
*/
|
|
168
|
+
async fill() {
|
|
169
|
+
if (this.parts !== null) {
|
|
170
|
+
this.parts.push(this.buf.slice(this.captureStart));
|
|
171
|
+
this.captureStart = 0;
|
|
172
|
+
}
|
|
173
|
+
this.linesBefore += countNewlines(this.buf, 0, this.buf.length);
|
|
174
|
+
for (; ; ) {
|
|
175
|
+
const next = await this.iterator.next();
|
|
176
|
+
if (next.done === true) {
|
|
177
|
+
this.buf = "";
|
|
178
|
+
this.pos = 0;
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (next.value.length > 0) {
|
|
182
|
+
this.buf = next.value;
|
|
183
|
+
this.pos = 0;
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The 1-based line of the read position.
|
|
190
|
+
* @returns the line
|
|
191
|
+
*/
|
|
192
|
+
line() {
|
|
193
|
+
return this.linesBefore + countNewlines(this.buf, 0, Math.min(this.pos, this.buf.length));
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Skip whitespace and return the next character without consuming it.
|
|
197
|
+
* @returns the character, or "" at the end of the input
|
|
198
|
+
*/
|
|
199
|
+
async peek() {
|
|
200
|
+
for (; ; ) {
|
|
201
|
+
while (this.pos < this.buf.length && isSpace(this.buf.charCodeAt(this.pos))) {
|
|
202
|
+
this.pos++;
|
|
203
|
+
}
|
|
204
|
+
if (this.pos < this.buf.length) {
|
|
205
|
+
return this.buf[this.pos];
|
|
206
|
+
}
|
|
207
|
+
if (!await this.fill()) {
|
|
208
|
+
return "";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** Start keeping the text from the read position. */
|
|
213
|
+
beginCapture() {
|
|
214
|
+
this.parts = [];
|
|
215
|
+
this.captureStart = this.pos;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Stop keeping text.
|
|
219
|
+
* @returns the text kept since beginCapture()
|
|
220
|
+
*/
|
|
221
|
+
endCapture() {
|
|
222
|
+
const parts = this.parts ?? [];
|
|
223
|
+
parts.push(this.buf.slice(this.captureStart, this.pos));
|
|
224
|
+
this.parts = null;
|
|
225
|
+
return parts.length === 1 ? parts[0] : parts.join("");
|
|
226
|
+
}
|
|
227
|
+
/** Drop the capture without building its text. */
|
|
228
|
+
dropCapture() {
|
|
229
|
+
this.parts = null;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Advance past one JSON value starting at the read position (which holds its first
|
|
233
|
+
* character), tracking strings and nesting only; the value is validated when it is parsed.
|
|
234
|
+
* @param startDepth - the nesting depth already entered (1 when the opening brace was consumed)
|
|
235
|
+
*/
|
|
236
|
+
async skipValue(startDepth = 0) {
|
|
237
|
+
let depth = startDepth;
|
|
238
|
+
this.deepest = depth;
|
|
239
|
+
if (depth === 0) {
|
|
240
|
+
const c = this.buf.charCodeAt(this.pos);
|
|
241
|
+
if (c === 34) {
|
|
242
|
+
this.pos++;
|
|
243
|
+
await this.skipString();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (c !== 123 && c !== 91) {
|
|
247
|
+
await this.skipScalar();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
for (; ; ) {
|
|
252
|
+
if (this.pos >= this.buf.length && !await this.fill()) {
|
|
253
|
+
throw new JsonScanError("the document ends inside a value", this.line());
|
|
254
|
+
}
|
|
255
|
+
const c = this.buf.charCodeAt(this.pos);
|
|
256
|
+
this.pos++;
|
|
257
|
+
if (c === 34) {
|
|
258
|
+
await this.skipString();
|
|
259
|
+
} else if (c === 123 || c === 91) {
|
|
260
|
+
depth++;
|
|
261
|
+
if (depth > this.deepest) {
|
|
262
|
+
this.deepest = depth;
|
|
263
|
+
}
|
|
264
|
+
} else if (c === 125 || c === 93) {
|
|
265
|
+
depth--;
|
|
266
|
+
if (depth === 0) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/** Advance past the rest of a string whose opening quote was consumed. */
|
|
273
|
+
async skipString() {
|
|
274
|
+
for (; ; ) {
|
|
275
|
+
QUOTE_OR_BACKSLASH.lastIndex = this.pos;
|
|
276
|
+
const found = QUOTE_OR_BACKSLASH.exec(this.buf);
|
|
277
|
+
if (found === null) {
|
|
278
|
+
this.pos = this.buf.length;
|
|
279
|
+
if (!await this.fill()) {
|
|
280
|
+
throw new JsonScanError("the document ends inside a string", this.line());
|
|
281
|
+
}
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
this.pos = found.index + 1;
|
|
285
|
+
if (found[0] === '"') {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (this.pos >= this.buf.length && !await this.fill()) {
|
|
289
|
+
throw new JsonScanError("the document ends inside a string", this.line());
|
|
290
|
+
}
|
|
291
|
+
this.pos++;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/** Advance past a bare scalar (a number, true, false, null or a stray word). */
|
|
295
|
+
async skipScalar() {
|
|
296
|
+
for (; ; ) {
|
|
297
|
+
while (this.pos < this.buf.length && !endsScalar(this.buf.charCodeAt(this.pos))) {
|
|
298
|
+
this.pos++;
|
|
299
|
+
}
|
|
300
|
+
if (this.pos < this.buf.length || !await this.fill()) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Read one JSON string literal at the read position (its opening quote).
|
|
307
|
+
* @returns the decoded string
|
|
308
|
+
*/
|
|
309
|
+
async readString() {
|
|
310
|
+
this.beginCapture();
|
|
311
|
+
this.pos++;
|
|
312
|
+
await this.skipString();
|
|
313
|
+
const literal = this.endCapture();
|
|
314
|
+
try {
|
|
315
|
+
return JSON.parse(literal);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
throw new JsonScanError(`invalid string: ${messageOf(err)}`, this.line());
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function countNewlines(text, from, to) {
|
|
322
|
+
let count = 0;
|
|
323
|
+
let i = text.indexOf("\n", from);
|
|
324
|
+
while (i >= 0 && i < to) {
|
|
325
|
+
count++;
|
|
326
|
+
i = text.indexOf("\n", i + 1);
|
|
327
|
+
}
|
|
328
|
+
return count;
|
|
329
|
+
}
|
|
330
|
+
function messageOf(err) {
|
|
331
|
+
return err instanceof Error ? err.message : String(err);
|
|
332
|
+
}
|
|
333
|
+
function parseAt(text, line) {
|
|
334
|
+
try {
|
|
335
|
+
return parseExact(text);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
throw new JsonScanError(`invalid JSON: ${messageOf(err)}`, line);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async function* scanAspects(chunks) {
|
|
341
|
+
const cur = new ChunkCursor(chunks);
|
|
342
|
+
const first = await cur.peek();
|
|
343
|
+
if (first === "") {
|
|
344
|
+
throw new JsonScanError("the input is empty", 1, true);
|
|
345
|
+
}
|
|
346
|
+
if (first !== "[") {
|
|
347
|
+
const line = cur.line();
|
|
348
|
+
cur.beginCapture();
|
|
349
|
+
await cur.skipValue();
|
|
350
|
+
const { value } = parseAt(cur.endCapture(), line);
|
|
351
|
+
if (await cur.peek() !== "") {
|
|
352
|
+
throw new JsonScanError("unexpected text after the document", cur.line());
|
|
353
|
+
}
|
|
354
|
+
yield { kind: "root", value, line };
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
cur.pos++;
|
|
358
|
+
let block = 0;
|
|
359
|
+
if (await cur.peek() === "]") {
|
|
360
|
+
cur.pos++;
|
|
361
|
+
} else {
|
|
362
|
+
for (; ; ) {
|
|
363
|
+
yield* member(cur, block);
|
|
364
|
+
block++;
|
|
365
|
+
const next = await cur.peek();
|
|
366
|
+
if (next === ",") {
|
|
367
|
+
cur.pos++;
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (next === "]") {
|
|
371
|
+
cur.pos++;
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
throw new JsonScanError(
|
|
375
|
+
next === "" ? "the document ends before its closing bracket" : `expected "," or "]", found "${next}"`,
|
|
376
|
+
cur.line()
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (await cur.peek() !== "") {
|
|
381
|
+
throw new JsonScanError("unexpected text after the closing bracket", cur.line());
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async function* member(cur, block) {
|
|
385
|
+
const c = await cur.peek();
|
|
386
|
+
const line = cur.line();
|
|
387
|
+
if (c === "" || c === "]" || c === ",") {
|
|
388
|
+
throw new JsonScanError(c === "" ? "the document ends inside its array" : `unexpected "${c}"`, line);
|
|
389
|
+
}
|
|
390
|
+
if (c !== "{") {
|
|
391
|
+
cur.beginCapture();
|
|
392
|
+
await cur.skipValue();
|
|
393
|
+
if (cur.deepest > MAX_ELEMENT_DEPTH) {
|
|
394
|
+
cur.dropCapture();
|
|
395
|
+
yield { kind: "deep", aspect: null, block, depth: cur.deepest, line };
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const parsed = parseAt(cur.endCapture(), line);
|
|
399
|
+
yield { kind: "member", block, value: parsed.value, exact: parsed.exact, line };
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
cur.beginCapture();
|
|
403
|
+
cur.pos++;
|
|
404
|
+
const k = await cur.peek();
|
|
405
|
+
if (k !== '"') {
|
|
406
|
+
await cur.skipValue(1);
|
|
407
|
+
if (cur.deepest > MAX_ELEMENT_DEPTH) {
|
|
408
|
+
cur.dropCapture();
|
|
409
|
+
yield { kind: "deep", aspect: null, block, depth: cur.deepest, line };
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const parsed = parseAt(cur.endCapture(), line);
|
|
413
|
+
yield { kind: "member", block, value: parsed.value, exact: parsed.exact, line };
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const outer = cur.endCapture();
|
|
417
|
+
const keyLine = cur.line();
|
|
418
|
+
const aspect = await cur.readString();
|
|
419
|
+
if (await cur.peek() !== ":") {
|
|
420
|
+
throw new JsonScanError(`expected ":" after the key "${aspect}"`, cur.line());
|
|
421
|
+
}
|
|
422
|
+
cur.pos++;
|
|
423
|
+
const v = await cur.peek();
|
|
424
|
+
if (v !== "[") {
|
|
425
|
+
cur.beginCapture();
|
|
426
|
+
await cur.skipValue(1);
|
|
427
|
+
if (cur.deepest > MAX_ELEMENT_DEPTH) {
|
|
428
|
+
cur.dropCapture();
|
|
429
|
+
yield { kind: "deep", aspect, block, depth: cur.deepest, line };
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const rest = cur.endCapture();
|
|
433
|
+
const text = `${outer}${JSON.stringify(aspect)}:${rest}`;
|
|
434
|
+
const parsed = parseAt(text, keyLine);
|
|
435
|
+
yield { kind: "member", block, value: parsed.value, exact: parsed.exact, line };
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
cur.pos++;
|
|
439
|
+
yield { kind: "block", aspect, block, line };
|
|
440
|
+
if (await cur.peek() === "]") {
|
|
441
|
+
cur.pos++;
|
|
442
|
+
} else {
|
|
443
|
+
for (; ; ) {
|
|
444
|
+
const e = await cur.peek();
|
|
445
|
+
if (e === "") {
|
|
446
|
+
throw new JsonScanError(`the document ends inside the "${aspect}" block`, cur.line());
|
|
447
|
+
}
|
|
448
|
+
const elementLine = cur.line();
|
|
449
|
+
cur.beginCapture();
|
|
450
|
+
await cur.skipValue();
|
|
451
|
+
if (cur.deepest > MAX_ELEMENT_DEPTH) {
|
|
452
|
+
cur.dropCapture();
|
|
453
|
+
yield { kind: "deep", aspect, block, depth: cur.deepest, line: elementLine };
|
|
454
|
+
} else {
|
|
455
|
+
const text = cur.endCapture();
|
|
456
|
+
const parsed = parseAt(text, elementLine);
|
|
457
|
+
yield {
|
|
458
|
+
kind: "element",
|
|
459
|
+
aspect,
|
|
460
|
+
block,
|
|
461
|
+
value: parsed.value,
|
|
462
|
+
exact: parsed.exact,
|
|
463
|
+
text,
|
|
464
|
+
line: elementLine
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const next = await cur.peek();
|
|
468
|
+
if (next === ",") {
|
|
469
|
+
cur.pos++;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (next === "]") {
|
|
473
|
+
cur.pos++;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
throw new JsonScanError(
|
|
477
|
+
next === "" ? `the document ends inside the "${aspect}" block` : `expected "," or "]" in the "${aspect}" block, found "${next}"`,
|
|
478
|
+
cur.line()
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const end = await cur.peek();
|
|
483
|
+
if (end === "}") {
|
|
484
|
+
cur.pos++;
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (end !== ",") {
|
|
488
|
+
throw new JsonScanError(
|
|
489
|
+
end === "" ? "the document ends inside a member" : `expected "}" after the "${aspect}" block`,
|
|
490
|
+
cur.line()
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
const keys = [];
|
|
494
|
+
const extraLine = cur.line();
|
|
495
|
+
while (await cur.peek() === ",") {
|
|
496
|
+
cur.pos++;
|
|
497
|
+
if (await cur.peek() !== '"') {
|
|
498
|
+
throw new JsonScanError("expected a key", cur.line());
|
|
499
|
+
}
|
|
500
|
+
keys.push(await cur.readString());
|
|
501
|
+
if (await cur.peek() !== ":") {
|
|
502
|
+
throw new JsonScanError('expected ":"', cur.line());
|
|
503
|
+
}
|
|
504
|
+
cur.pos++;
|
|
505
|
+
await cur.peek();
|
|
506
|
+
cur.beginCapture();
|
|
507
|
+
await cur.skipValue();
|
|
508
|
+
parseAt(cur.endCapture(), cur.line());
|
|
509
|
+
}
|
|
510
|
+
if (await cur.peek() !== "}") {
|
|
511
|
+
throw new JsonScanError('expected "}"', cur.line());
|
|
512
|
+
}
|
|
513
|
+
cur.pos++;
|
|
514
|
+
yield { kind: "extraKeys", aspect, block, keys, line: extraLine };
|
|
515
|
+
}
|
|
516
|
+
function reportTooDeep(report, event) {
|
|
517
|
+
report.error(
|
|
518
|
+
"parse-error",
|
|
519
|
+
BAD_ASPECT_BLOCK_CODE,
|
|
520
|
+
`${event.aspect === null ? "a member of the document" : `an element of "${event.aspect}"`} is nested ${event.depth} levels deep, more than the ${MAX_ELEMENT_DEPTH} a graph keeps; skipped`,
|
|
521
|
+
{ line: event.line, element: event.aspect }
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
const DECIMAL_INTEGER_TEXT = /^-?(0|[1-9][0-9]*)$/;
|
|
525
|
+
function cxId(raw, inexactLiteral2 = false) {
|
|
526
|
+
if (raw instanceof ExactInteger) {
|
|
527
|
+
return { id: raw.digits, note: "precision" };
|
|
528
|
+
}
|
|
529
|
+
if (typeof raw === "number") {
|
|
530
|
+
if (Number.isSafeInteger(raw)) {
|
|
531
|
+
return { id: raw === 0 ? 0 : raw, note: inexactLiteral2 ? "text" : null };
|
|
532
|
+
}
|
|
533
|
+
throw invalidCxId(raw, Number.isInteger(raw) ? "an integer beyond 2^53 written inexactly" : "not an integer");
|
|
534
|
+
}
|
|
535
|
+
if (typeof raw === "string" && raw !== "-0" && DECIMAL_INTEGER_TEXT.test(raw)) {
|
|
536
|
+
const n = Number(raw);
|
|
537
|
+
return Number.isSafeInteger(n) ? { id: n, note: "text" } : { id: raw, note: "precision" };
|
|
538
|
+
}
|
|
539
|
+
throw invalidCxId(raw, "a CX id is an integer");
|
|
540
|
+
}
|
|
541
|
+
function invalidCxId(raw, reason) {
|
|
542
|
+
const shown = typeof raw === "string" || typeof raw === "number" ? JSON.stringify(raw) : typeof raw;
|
|
543
|
+
return new GraphFormatError("E_INVALID_ID", `invalid id ${raw === null ? "null" : shown}: ${reason}`, {
|
|
544
|
+
reason
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
function inexactLiteral(text, key) {
|
|
548
|
+
const quoted = JSON.stringify(key);
|
|
549
|
+
let depth = 0;
|
|
550
|
+
for (let i = 0; i < text.length; i++) {
|
|
551
|
+
const c = text[i];
|
|
552
|
+
if (c === "{" || c === "[") {
|
|
553
|
+
depth++;
|
|
554
|
+
} else if (c === "}" || c === "]") {
|
|
555
|
+
depth--;
|
|
556
|
+
} else if (c === '"') {
|
|
557
|
+
const start = i;
|
|
558
|
+
for (i++; i < text.length && text[i] !== '"'; i++) {
|
|
559
|
+
if (text[i] === "\\") {
|
|
560
|
+
i++;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
if (depth === 1 && text.startsWith(quoted, start) && /^\s*:/.test(text.slice(i + 1, i + 16))) {
|
|
564
|
+
const match = /^\s*:\s*(-?[0-9][0-9.eE+-]*)/.exec(text.slice(i + 1, i + 64));
|
|
565
|
+
return match !== null && /[.eE]/.test(match[1]);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
const POSITION_COLUMN = "position";
|
|
572
|
+
const Z_COLUMN = "z";
|
|
573
|
+
function positionDecl(format, sourceDims = 2) {
|
|
574
|
+
return {
|
|
575
|
+
name: POSITION_COLUMN,
|
|
576
|
+
dtype: "f32",
|
|
577
|
+
components: 3,
|
|
578
|
+
role: "position",
|
|
579
|
+
mutable: true,
|
|
580
|
+
nullable: true,
|
|
581
|
+
extra: { sourceDims, units: "file" },
|
|
582
|
+
origin: { format, namespace: "cytoscape" }
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
function zDecl(format) {
|
|
586
|
+
return {
|
|
587
|
+
name: Z_COLUMN,
|
|
588
|
+
dtype: "f64",
|
|
589
|
+
nullable: true,
|
|
590
|
+
origin: { format, id: null, namespace: "cytoscape" },
|
|
591
|
+
extra: { cytoscape: "z" }
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function flipY(y) {
|
|
595
|
+
return y === 0 ? 0 : -y;
|
|
596
|
+
}
|
|
597
|
+
const STRUCTURE_ASPECTS = /* @__PURE__ */ new Set(["metaData", "status", "numberVerification"]);
|
|
598
|
+
class CxStructure {
|
|
599
|
+
/**
|
|
600
|
+
* Create the checker.
|
|
601
|
+
* @param report - the report
|
|
602
|
+
*/
|
|
603
|
+
constructor(report) {
|
|
604
|
+
this.counts = /* @__PURE__ */ new Map();
|
|
605
|
+
this.blocks = /* @__PURE__ */ new Map();
|
|
606
|
+
this.declared = /* @__PURE__ */ new Map();
|
|
607
|
+
this.metaDataBlocks = 0;
|
|
608
|
+
this.networkBlocks = 0;
|
|
609
|
+
this.postMetadata = false;
|
|
610
|
+
this.statusSeen = false;
|
|
611
|
+
this.status = void 0;
|
|
612
|
+
this.report = report;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Whether a status block was read.
|
|
616
|
+
* @returns true once a status block began
|
|
617
|
+
*/
|
|
618
|
+
get hasStatus() {
|
|
619
|
+
return this.statusSeen;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Record the start of a block (or a member holding one aspect).
|
|
623
|
+
* @param aspect - the aspect name
|
|
624
|
+
* @param line - its line
|
|
625
|
+
*/
|
|
626
|
+
block(aspect, line) {
|
|
627
|
+
this.blocks.set(aspect, (this.blocks.get(aspect) ?? 0) + 1);
|
|
628
|
+
if (this.statusSeen) {
|
|
629
|
+
this.report.warnOnce(
|
|
630
|
+
"validation-error",
|
|
631
|
+
ASPECT_ORDER_CODE,
|
|
632
|
+
`the "${aspect}" block comes after the status block, which must be last; it is read`,
|
|
633
|
+
{ line, element: aspect },
|
|
634
|
+
`${ASPECT_ORDER_CODE}:status`
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
if (aspect === "metaData") {
|
|
638
|
+
this.metaDataBlocks++;
|
|
639
|
+
if (this.networkBlocks > 0) {
|
|
640
|
+
this.postMetadata = true;
|
|
641
|
+
}
|
|
642
|
+
if (this.metaDataBlocks > 2) {
|
|
643
|
+
this.report.warnOnce(
|
|
644
|
+
"validation-error",
|
|
645
|
+
ASPECT_ORDER_CODE,
|
|
646
|
+
`${this.metaDataBlocks} metaData blocks; a document has at most two (pre and post)`,
|
|
647
|
+
{ line, element: aspect },
|
|
648
|
+
`${ASPECT_ORDER_CODE}:metaData`
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (aspect === "status") {
|
|
654
|
+
this.statusSeen = true;
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (STRUCTURE_ASPECTS.has(aspect)) {
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
this.networkBlocks++;
|
|
661
|
+
if (this.postMetadata) {
|
|
662
|
+
this.report.warnOnce(
|
|
663
|
+
"validation-error",
|
|
664
|
+
ASPECT_ORDER_CODE,
|
|
665
|
+
`the "${aspect}" block comes after the post-metadata, where only status is allowed; it is read`,
|
|
666
|
+
{ line, element: aspect },
|
|
667
|
+
`${ASPECT_ORDER_CODE}:post`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Record one element of a block; metaData and status elements are checked here.
|
|
673
|
+
* @param aspect - the aspect name
|
|
674
|
+
* @param value - the parsed element
|
|
675
|
+
* @param line - its line
|
|
676
|
+
*/
|
|
677
|
+
element(aspect, value, line) {
|
|
678
|
+
this.counts.set(aspect, (this.counts.get(aspect) ?? 0) + 1);
|
|
679
|
+
if (aspect === "metaData") {
|
|
680
|
+
if (isRecord(value) && typeof value.name === "string" && typeof value.elementCount === "number") {
|
|
681
|
+
this.declared.set(value.name, value.elementCount);
|
|
682
|
+
}
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (aspect === "status") {
|
|
686
|
+
this.readStatus(value, line);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Check a status element: success false is fatal, an error text with success true a warning.
|
|
691
|
+
* @param value - the element
|
|
692
|
+
* @param line - its line
|
|
693
|
+
*/
|
|
694
|
+
readStatus(value, line) {
|
|
695
|
+
if (this.status !== void 0) {
|
|
696
|
+
this.report.warnOnce(
|
|
697
|
+
"validation-error",
|
|
698
|
+
ASPECT_ORDER_CODE,
|
|
699
|
+
"more than one status element; the last is used",
|
|
700
|
+
{ line, element: "status" },
|
|
701
|
+
`${ASPECT_ORDER_CODE}:status2`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
this.status = value;
|
|
705
|
+
if (!isRecord(value)) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const error = typeof value.error === "string" ? value.error : "";
|
|
709
|
+
if (value.success === false) {
|
|
710
|
+
const message = `the producer marked the document as failed${error.length > 0 ? `: ${error}` : ""}`;
|
|
711
|
+
this.report.error("validation-error", STATUS_FAILED_CODE, message, { line, element: "status" });
|
|
712
|
+
throw this.report.abort(message, { code: STATUS_FAILED_CODE });
|
|
713
|
+
}
|
|
714
|
+
if (value.success === true && error.length > 0) {
|
|
715
|
+
this.report.warning("validation-error", STATUS_WARNING_CODE, `the producer reports a warning: ${error}`, {
|
|
716
|
+
line,
|
|
717
|
+
element: "status"
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Whether the status element is one object with a boolean success.
|
|
723
|
+
* @returns true for a well-formed status
|
|
724
|
+
*/
|
|
725
|
+
statusWellFormed() {
|
|
726
|
+
return isRecord(this.status) && typeof this.status.success === "boolean";
|
|
727
|
+
}
|
|
728
|
+
/** Compare the declared element counts with what was read (W_COUNT_MISMATCH per aspect). */
|
|
729
|
+
checkCounts() {
|
|
730
|
+
for (const [name, declared] of this.declared) {
|
|
731
|
+
const read = this.counts.get(name) ?? 0;
|
|
732
|
+
if (read !== declared) {
|
|
733
|
+
this.report.warning(
|
|
734
|
+
"validation-error",
|
|
735
|
+
COUNT_MISMATCH_CODE,
|
|
736
|
+
`metaData declares ${declared} "${name}" element(s); ${read} were read`,
|
|
737
|
+
{ element: name }
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function isRecord(value) {
|
|
744
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof ExactInteger);
|
|
745
|
+
}
|
|
746
|
+
function declareFresh(sink, domain, decl, report) {
|
|
747
|
+
const taken = (name) => (domain === "node" ? sink.nodeColumn(name) : sink.edgeColumn(name)) !== INVALID_INDEX;
|
|
748
|
+
let fresh = decl;
|
|
749
|
+
if (taken(decl.name)) {
|
|
750
|
+
fresh = { ...decl, name: uniqueColumnName(decl.name, null, taken) };
|
|
751
|
+
report.warning(
|
|
752
|
+
"coercion",
|
|
753
|
+
COLUMN_RENAMED_CODE,
|
|
754
|
+
`${domain} column "${decl.name}" renamed to "${fresh.name}": an attribute holds that name`,
|
|
755
|
+
{ element: decl.name }
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
return declareResolved(sink, domain, fresh, report).handle;
|
|
759
|
+
}
|
|
760
|
+
export {
|
|
761
|
+
CxStructure as C,
|
|
762
|
+
ExactInteger as E,
|
|
763
|
+
JsonScanError as J,
|
|
764
|
+
MAYBE_UNSAFE_INTEGER as M,
|
|
765
|
+
POSITION_COLUMN as P,
|
|
766
|
+
positionDecl as a,
|
|
767
|
+
inexactLiteral as b,
|
|
768
|
+
cxId as c,
|
|
769
|
+
declareFresh as d,
|
|
770
|
+
rewriteNumbers as e,
|
|
771
|
+
flipY as f,
|
|
772
|
+
reviveNonstandard as g,
|
|
773
|
+
isRecord as i,
|
|
774
|
+
plainJson as p,
|
|
775
|
+
reportTooDeep as r,
|
|
776
|
+
scanAspects as s,
|
|
777
|
+
zDecl as z
|
|
778
|
+
};
|
|
779
|
+
//# sourceMappingURL=json-elements-CZY1wiZh.js.map
|