@graphty/graph-io 0.3.39 → 0.3.41
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/dist/chunks/{choose-CS3fvGT1.js → choose-CwFQamH9.js} +2 -2
- package/dist/chunks/{choose-CS3fvGT1.js.map → choose-CwFQamH9.js.map} +1 -1
- package/dist/chunks/{constants-DqKZT6cH.js → constants-Cf4Nn6tw.js} +2 -2
- package/dist/chunks/{constants-DqKZT6cH.js.map → constants-Cf4Nn6tw.js.map} +1 -1
- package/dist/chunks/{escape-BpYMQZVa.js → escape-DBb8zuNE.js} +4 -3
- package/dist/chunks/escape-DBb8zuNE.js.map +1 -0
- package/dist/chunks/{export-B-aVCwyG.js → export-DD3Lvw-F.js} +173 -116
- package/dist/chunks/export-DD3Lvw-F.js.map +1 -0
- package/dist/chunks/{exporter-CNJZF_BN.js → exporter-BqtmlxNr.js} +545 -459
- package/dist/chunks/exporter-BqtmlxNr.js.map +1 -0
- package/dist/chunks/{exporter-BqKs1Q12.js → exporter-CS35R6Fg.js} +6 -5
- package/dist/chunks/exporter-CS35R6Fg.js.map +1 -0
- package/dist/chunks/{exporter-DXqCPNrI.js → exporter-D8r1N2dU.js} +67 -42
- package/dist/chunks/exporter-D8r1N2dU.js.map +1 -0
- package/dist/chunks/{exporter-SFYkDzD0.js → exporter-NMplSnYn.js} +23 -20
- package/dist/chunks/{exporter-SFYkDzD0.js.map → exporter-NMplSnYn.js.map} +1 -1
- package/dist/chunks/{importer-CSt-gaYE.js → importer-BKC7AmCA.js} +42 -29
- package/dist/chunks/importer-BKC7AmCA.js.map +1 -0
- package/dist/chunks/{importer-DvFznt5-.js → importer-BdWHmlmG.js} +112 -62
- package/dist/chunks/importer-BdWHmlmG.js.map +1 -0
- package/dist/chunks/{importer-CqgYq4Ld.js → importer-BfbzWk6k.js} +202 -181
- package/dist/chunks/importer-BfbzWk6k.js.map +1 -0
- package/dist/chunks/{importer-PVXkeoaS.js → importer-BmaZcskq.js} +79 -66
- package/dist/chunks/importer-BmaZcskq.js.map +1 -0
- package/dist/chunks/{importer-B7Ir269L.js → importer-DviEsHy4.js} +118 -72
- package/dist/chunks/importer-DviEsHy4.js.map +1 -0
- package/dist/chunks/{importer--tQQsY4W.js → importer-S-abxJHn.js} +5 -5
- package/dist/chunks/{importer--tQQsY4W.js.map → importer-S-abxJHn.js.map} +1 -1
- package/dist/chunks/{importer-BfJ1jnNr.js → importer-r29cWnEf.js} +28 -13
- package/dist/chunks/importer-r29cWnEf.js.map +1 -0
- package/dist/chunks/{json-elements-CXs4z1iL.js → json-elements-DdF-etPT.js} +7 -5
- package/dist/chunks/{json-elements-CXs4z1iL.js.map → json-elements-DdF-etPT.js.map} +1 -1
- package/dist/chunks/{ontology-export-50L9DOV4.js → ontology-export-BC0zyPhj.js} +2 -2
- package/dist/chunks/{ontology-export-50L9DOV4.js.map → ontology-export-BC0zyPhj.js.map} +1 -1
- package/dist/chunks/{records-C9jd7yLD.js → records-Uc3LkABx.js} +150 -106
- package/dist/chunks/records-Uc3LkABx.js.map +1 -0
- package/dist/chunks/{text-Bjfa1xYL.js → text-Bh9SaAob.js} +20 -11
- package/dist/chunks/text-Bh9SaAob.js.map +1 -0
- package/dist/csv.js +67 -49
- package/dist/csv.js.map +1 -1
- package/dist/cx.js +1 -1
- package/dist/cx2.js +2 -2
- package/dist/cys.js +1 -1
- package/dist/dot.js +1 -1
- package/dist/gexf.js +199 -99
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +170 -149
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +26 -26
- package/dist/graphml.js +2 -2
- package/dist/json.js +1 -1
- package/dist/neo4j.js +103 -65
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +34 -9
- package/dist/src/common/attributes.js.map +1 -1
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +180 -102
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/json-elements.d.ts.map +1 -1
- package/dist/src/common/json-elements.js +4 -2
- package/dist/src/common/json-elements.js.map +1 -1
- package/dist/src/common/text.d.ts +7 -0
- package/dist/src/common/text.d.ts.map +1 -1
- package/dist/src/common/text.js +15 -7
- package/dist/src/common/text.js.map +1 -1
- package/dist/src/common/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +2 -1
- package/dist/src/common/xml.js.map +1 -1
- package/dist/src/common/zip.d.ts.map +1 -1
- package/dist/src/common/zip.js +36 -19
- package/dist/src/common/zip.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +77 -25
- package/dist/src/formats/csv/exporter.js.map +1 -1
- package/dist/src/formats/csv/importer.d.ts.map +1 -1
- package/dist/src/formats/csv/importer.js +16 -6
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +156 -110
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/cx/importer.d.ts.map +1 -1
- package/dist/src/formats/cx/importer.js +108 -53
- package/dist/src/formats/cx/importer.js.map +1 -1
- package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
- package/dist/src/formats/cx2/exporter.js +2 -1
- package/dist/src/formats/cx2/exporter.js.map +1 -1
- package/dist/src/formats/cx2/importer.d.ts.map +1 -1
- package/dist/src/formats/cx2/importer.js +28 -6
- package/dist/src/formats/cx2/importer.js.map +1 -1
- package/dist/src/formats/cys/tables.d.ts.map +1 -1
- package/dist/src/formats/cys/tables.js +11 -8
- package/dist/src/formats/cys/tables.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +69 -39
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +138 -50
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +51 -35
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +103 -58
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +17 -8
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +36 -25
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +20 -15
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/tree.js +23 -14
- package/dist/src/formats/graphml/tree.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +22 -10
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +38 -21
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +536 -343
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/obographs.js +29 -17
- package/dist/src/formats/json/obographs.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +73 -43
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/neo4j/header.d.ts.map +1 -1
- package/dist/src/formats/neo4j/header.js +37 -16
- package/dist/src/formats/neo4j/header.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +21 -11
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/obo/exporter.d.ts.map +1 -1
- package/dist/src/formats/obo/exporter.js +18 -7
- package/dist/src/formats/obo/exporter.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +142 -73
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +26 -15
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +85 -58
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +13 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +112 -50
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
- package/dist/src/formats/xgmml/exporter.js +92 -34
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
- package/dist/src/formats/xgmml/importer.js +38 -18
- package/dist/src/formats/xgmml/importer.js.map +1 -1
- package/dist/src/sniff.js +109 -57
- package/dist/src/sniff.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/package.json +2 -2
- package/src/common/attributes.ts +59 -19
- package/src/common/input.ts +216 -102
- package/src/common/json-elements.ts +4 -2
- package/src/common/text.ts +24 -15
- package/src/common/xml.ts +2 -1
- package/src/common/zip.ts +47 -18
- package/src/formats/csv/exporter.ts +103 -39
- package/src/formats/csv/importer.ts +17 -6
- package/src/formats/csv/records.ts +162 -109
- package/src/formats/cx/importer.ts +140 -58
- package/src/formats/cx2/exporter.ts +2 -1
- package/src/formats/cx2/importer.ts +34 -10
- package/src/formats/cys/tables.ts +11 -10
- package/src/formats/gexf/exporter.ts +71 -38
- package/src/formats/gexf/importer.ts +173 -63
- package/src/formats/gml/exporter.ts +78 -82
- package/src/formats/gml/importer.ts +119 -68
- package/src/formats/gml/syntax.ts +18 -8
- package/src/formats/graphml/exporter.ts +46 -27
- package/src/formats/graphml/importer.ts +21 -13
- package/src/formats/graphml/tree.ts +22 -12
- package/src/formats/graphml/yfiles.ts +23 -10
- package/src/formats/json/exporter.ts +40 -21
- package/src/formats/json/importer.ts +701 -385
- package/src/formats/json/obographs.ts +35 -15
- package/src/formats/neo4j/exporter.ts +79 -41
- package/src/formats/neo4j/header.ts +50 -16
- package/src/formats/neo4j/importer.ts +21 -10
- package/src/formats/obo/exporter.ts +34 -17
- package/src/formats/pajek/exporter.ts +180 -117
- package/src/formats/pajek/importer.ts +27 -15
- package/src/formats/pajek/syntax.ts +99 -58
- package/src/formats/xgmml/emit.ts +122 -50
- package/src/formats/xgmml/exporter.ts +103 -35
- package/src/formats/xgmml/importer.ts +45 -23
- package/src/sniff.ts +115 -59
- package/dist/chunks/escape-BpYMQZVa.js.map +0 -1
- package/dist/chunks/export-B-aVCwyG.js.map +0 -1
- package/dist/chunks/exporter-BqKs1Q12.js.map +0 -1
- package/dist/chunks/exporter-CNJZF_BN.js.map +0 -1
- package/dist/chunks/exporter-DXqCPNrI.js.map +0 -1
- package/dist/chunks/importer-B7Ir269L.js.map +0 -1
- package/dist/chunks/importer-BfJ1jnNr.js.map +0 -1
- package/dist/chunks/importer-CSt-gaYE.js.map +0 -1
- package/dist/chunks/importer-CqgYq4Ld.js.map +0 -1
- package/dist/chunks/importer-DvFznt5-.js.map +0 -1
- package/dist/chunks/importer-PVXkeoaS.js.map +0 -1
- package/dist/chunks/records-C9jd7yLD.js.map +0 -1
- package/dist/chunks/text-Bjfa1xYL.js.map +0 -1
package/src/common/input.ts
CHANGED
|
@@ -1232,130 +1232,244 @@ export async function* textChunks(
|
|
|
1232
1232
|
): AsyncGenerator<string, void, undefined> {
|
|
1233
1233
|
const input = normalizeInput(rawInput);
|
|
1234
1234
|
const signal = options.signal ?? null;
|
|
1235
|
-
const onProgress = options.onProgress ?? null;
|
|
1236
1235
|
const filter = new TextFilter(report, options);
|
|
1237
1236
|
throwIfAborted(signal);
|
|
1238
1237
|
if (typeof input === "string") {
|
|
1239
1238
|
reportTextEncoding(report, options.encoding);
|
|
1240
|
-
|
|
1241
|
-
let done = 0;
|
|
1242
|
-
let start = 0;
|
|
1243
|
-
while (start < input.length) {
|
|
1244
|
-
throwIfAborted(signal);
|
|
1245
|
-
let end = Math.min(start + DECODE_SLICE, input.length);
|
|
1246
|
-
const highSurrogate = (input.charCodeAt(end - 1) & 0xfc00) === 0xd800; // NOSONAR(S7758): reads UTF-16 code units on purpose
|
|
1247
|
-
if (end < input.length && highSurrogate) {
|
|
1248
|
-
end++; // never split a surrogate pair
|
|
1249
|
-
}
|
|
1250
|
-
const piece = start === 0 && end === input.length ? input : input.slice(start, end);
|
|
1251
|
-
start = end;
|
|
1252
|
-
const text = filter.push(piece);
|
|
1253
|
-
if (text.length > 0) {
|
|
1254
|
-
yield text;
|
|
1255
|
-
}
|
|
1256
|
-
if (onProgress !== null) {
|
|
1257
|
-
done += utf8Length(piece);
|
|
1258
|
-
onProgress(done, total);
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
if (input.length === 0) {
|
|
1262
|
-
onProgress?.(0, 0);
|
|
1263
|
-
}
|
|
1264
|
-
filter.end();
|
|
1239
|
+
yield* stringTextChunks(input, filter, options);
|
|
1265
1240
|
return;
|
|
1266
1241
|
}
|
|
1267
1242
|
const decoder = new ByteDecoder(report, options);
|
|
1268
|
-
const headBytes = Math.max(options.declarationBytes ?? HEAD_BYTES, BOM_BYTES);
|
|
1269
1243
|
if (input instanceof Uint8Array) {
|
|
1270
|
-
|
|
1271
|
-
decoder.start(input.subarray(0, headBytes));
|
|
1272
|
-
for (let offset = 0; offset < length; offset += DECODE_SLICE) {
|
|
1273
|
-
throwIfAborted(signal);
|
|
1274
|
-
if (input.byteLength !== length) {
|
|
1275
|
-
throw new GraphFormatError(
|
|
1276
|
-
"E_UNSUPPORTED",
|
|
1277
|
-
"the input's buffer was detached (transferred) or resized during the import",
|
|
1278
|
-
{ reason: "detached" },
|
|
1279
|
-
);
|
|
1280
|
-
}
|
|
1281
|
-
const slice = input.subarray(offset, Math.min(offset + DECODE_SLICE, length));
|
|
1282
|
-
const text = filter.push(decoder.decode(slice, offset + DECODE_SLICE < length));
|
|
1283
|
-
if (text.length > 0) {
|
|
1284
|
-
yield text;
|
|
1285
|
-
}
|
|
1286
|
-
onProgress?.(Math.min(offset + DECODE_SLICE, length), length);
|
|
1287
|
-
}
|
|
1288
|
-
if (length === 0) {
|
|
1289
|
-
onProgress?.(0, 0);
|
|
1290
|
-
}
|
|
1291
|
-
filter.end();
|
|
1244
|
+
yield* byteTextChunks(input, filter, decoder, options);
|
|
1292
1245
|
return;
|
|
1293
1246
|
}
|
|
1294
|
-
// the
|
|
1295
|
-
//
|
|
1296
|
-
|
|
1297
|
-
|
|
1247
|
+
// the stream's loop stays here rather than in a delegated generator: a yield* layer costs
|
|
1248
|
+
// time on every chunk, and a stream may arrive in many small ones
|
|
1249
|
+
const stream = new StreamText(filter, decoder, options);
|
|
1250
|
+
for await (const chunk of isReadableStream(input) ? streamChunks(input, signal) : input) {
|
|
1251
|
+
throwIfAborted(signal);
|
|
1252
|
+
const text = stream.push(chunk);
|
|
1253
|
+
if (text.length > 0) {
|
|
1254
|
+
yield text;
|
|
1255
|
+
}
|
|
1256
|
+
options.onProgress?.(stream.done);
|
|
1257
|
+
}
|
|
1258
|
+
const tail = stream.end();
|
|
1259
|
+
if (tail.length > 0) {
|
|
1260
|
+
yield tail;
|
|
1261
|
+
}
|
|
1262
|
+
filter.end();
|
|
1263
|
+
options.onProgress?.(stream.done, stream.done);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* The end of the slice of a string input that starts at start: DECODE_SLICE code units, one more
|
|
1268
|
+
* when the slice would end between the two halves of a surrogate pair.
|
|
1269
|
+
* @param input - the string
|
|
1270
|
+
* @param start - where the slice starts
|
|
1271
|
+
* @returns where it ends
|
|
1272
|
+
*/
|
|
1273
|
+
function stringSliceEnd(input: string, start: number): number {
|
|
1274
|
+
const end = Math.min(start + DECODE_SLICE, input.length);
|
|
1275
|
+
const highSurrogate = (input.charCodeAt(end - 1) & 0xfc00) === 0xd800; // NOSONAR(S7758): reads UTF-16 code units on purpose
|
|
1276
|
+
return end < input.length && highSurrogate ? end + 1 : end;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* textChunks() of a string input: slices of it, through the text filter.
|
|
1281
|
+
* @param input - the string
|
|
1282
|
+
* @param filter - the text checks
|
|
1283
|
+
* @param options - cancellation and progress
|
|
1284
|
+
* @yields the filtered slices
|
|
1285
|
+
* @returns nothing
|
|
1286
|
+
*/
|
|
1287
|
+
function* stringTextChunks(
|
|
1288
|
+
input: string,
|
|
1289
|
+
filter: TextFilter,
|
|
1290
|
+
options: ReadOptions,
|
|
1291
|
+
): Generator<string, void, undefined> {
|
|
1292
|
+
const signal = options.signal ?? null;
|
|
1293
|
+
const onProgress = options.onProgress ?? null;
|
|
1294
|
+
const total = onProgress === null ? 0 : utf8Length(input);
|
|
1298
1295
|
let done = 0;
|
|
1299
|
-
let
|
|
1300
|
-
|
|
1301
|
-
const flushHead = (stream: boolean): string => {
|
|
1302
|
-
const bytes = concatBytes(head);
|
|
1303
|
-
head = [];
|
|
1304
|
-
headLength = 0;
|
|
1305
|
-
decoder.start(bytes.subarray(0, headBytes));
|
|
1306
|
-
return decoder.decode(bytes, stream);
|
|
1307
|
-
};
|
|
1308
|
-
const chunks = isReadableStream(input) ? streamChunks(input, signal) : input;
|
|
1309
|
-
for await (const chunk of chunks) {
|
|
1296
|
+
let start = 0;
|
|
1297
|
+
while (start < input.length) {
|
|
1310
1298
|
throwIfAborted(signal);
|
|
1311
|
-
|
|
1312
|
-
const
|
|
1299
|
+
const end = stringSliceEnd(input, start);
|
|
1300
|
+
const piece = start === 0 && end === input.length ? input : input.slice(start, end);
|
|
1301
|
+
start = end;
|
|
1302
|
+
const text = filter.push(piece);
|
|
1303
|
+
if (text.length > 0) {
|
|
1304
|
+
yield text;
|
|
1305
|
+
}
|
|
1306
|
+
if (onProgress !== null) {
|
|
1307
|
+
done += utf8Length(piece);
|
|
1308
|
+
onProgress(done, total);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
if (input.length === 0) {
|
|
1312
|
+
onProgress?.(0, 0);
|
|
1313
|
+
}
|
|
1314
|
+
filter.end();
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* textChunks() of a Uint8Array input: slices of it, decoded, through the text filter.
|
|
1319
|
+
* @param input - the bytes
|
|
1320
|
+
* @param filter - the text checks
|
|
1321
|
+
* @param decoder - the byte decoder
|
|
1322
|
+
* @param options - cancellation, progress and the declaration size
|
|
1323
|
+
* @yields the decoded, filtered slices
|
|
1324
|
+
* @returns nothing
|
|
1325
|
+
*/
|
|
1326
|
+
function* byteTextChunks(
|
|
1327
|
+
input: Uint8Array,
|
|
1328
|
+
filter: TextFilter,
|
|
1329
|
+
decoder: ByteDecoder,
|
|
1330
|
+
options: ReadOptions,
|
|
1331
|
+
): Generator<string, void, undefined> {
|
|
1332
|
+
const signal = options.signal ?? null;
|
|
1333
|
+
const length = input.byteLength;
|
|
1334
|
+
decoder.start(input.subarray(0, headByteCount(options)));
|
|
1335
|
+
for (let offset = 0; offset < length; offset += DECODE_SLICE) {
|
|
1336
|
+
throwIfAborted(signal);
|
|
1337
|
+
if (input.byteLength !== length) {
|
|
1338
|
+
throw new GraphFormatError(
|
|
1339
|
+
"E_UNSUPPORTED",
|
|
1340
|
+
"the input's buffer was detached (transferred) or resized during the import",
|
|
1341
|
+
{ reason: "detached" },
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
const slice = input.subarray(offset, Math.min(offset + DECODE_SLICE, length));
|
|
1345
|
+
const text = filter.push(decoder.decode(slice, offset + DECODE_SLICE < length));
|
|
1346
|
+
if (text.length > 0) {
|
|
1347
|
+
yield text;
|
|
1348
|
+
}
|
|
1349
|
+
options.onProgress?.(Math.min(offset + DECODE_SLICE, length), length);
|
|
1350
|
+
}
|
|
1351
|
+
if (length === 0) {
|
|
1352
|
+
options.onProgress?.(0, 0);
|
|
1353
|
+
}
|
|
1354
|
+
filter.end();
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* How many leading bytes the encoding is chosen from: the format's declaration size, at least a BOM.
|
|
1359
|
+
* @param options - the declaration size
|
|
1360
|
+
* @returns the byte count
|
|
1361
|
+
*/
|
|
1362
|
+
function headByteCount(options: ReadOptions): number {
|
|
1363
|
+
return Math.max(options.declarationBytes ?? HEAD_BYTES, BOM_BYTES);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* The chunks of a stream input (a ReadableStream or an async iterable of strings and bytes) turned
|
|
1368
|
+
* into filtered text. The first bytes are held back until the BOM check (and the declaration
|
|
1369
|
+
* check, when the format has one) can see enough of them, or the input ended, or text arrived, so
|
|
1370
|
+
* a BOM or a declaration split across small chunks is still seen.
|
|
1371
|
+
*/
|
|
1372
|
+
class StreamText {
|
|
1373
|
+
/** Bytes read so far (text counted as UTF-8 when progress is reported), for progress. */
|
|
1374
|
+
done = 0;
|
|
1375
|
+
|
|
1376
|
+
private head: Uint8Array[] = [];
|
|
1377
|
+
|
|
1378
|
+
private headLength = 0;
|
|
1379
|
+
|
|
1380
|
+
private readonly headBytes: number;
|
|
1381
|
+
|
|
1382
|
+
/** How many bytes to hold back before choosing the encoding. */
|
|
1383
|
+
private readonly needed: number;
|
|
1384
|
+
|
|
1385
|
+
/** Whether progress is reported, so text chunks are measured. */
|
|
1386
|
+
private readonly counting: boolean;
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Create the reader of one stream input.
|
|
1390
|
+
* @param filter - the text checks
|
|
1391
|
+
* @param decoder - the byte decoder
|
|
1392
|
+
* @param options - progress, the declaration reader and its size
|
|
1393
|
+
*/
|
|
1394
|
+
constructor(
|
|
1395
|
+
private readonly filter: TextFilter,
|
|
1396
|
+
private readonly decoder: ByteDecoder,
|
|
1397
|
+
options: ReadOptions,
|
|
1398
|
+
) {
|
|
1399
|
+
this.headBytes = headByteCount(options);
|
|
1400
|
+
this.needed = options.declaredEncoding === undefined ? BOM_BYTES : this.headBytes;
|
|
1401
|
+
this.counting = (options.onProgress ?? null) !== null;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/**
|
|
1405
|
+
* Read the next chunk.
|
|
1406
|
+
* @param chunk - a string or a Uint8Array (anything else is E_UNSUPPORTED)
|
|
1407
|
+
* @returns its filtered text; empty while bytes are held back
|
|
1408
|
+
*/
|
|
1409
|
+
push(chunk: unknown): string {
|
|
1313
1410
|
if (typeof chunk === "string") {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
pending = decoder.flushBeforeText(() => flushHead(false));
|
|
1320
|
-
} else if (decoder.started) {
|
|
1321
|
-
pending = decoder.flushBeforeText(() => decoder.decode(new Uint8Array(0), false));
|
|
1322
|
-
} else {
|
|
1323
|
-
decoder.start(new Uint8Array(0));
|
|
1324
|
-
}
|
|
1325
|
-
text = filter.push(pending + chunk);
|
|
1326
|
-
done += onProgress === null ? 0 : utf8Length(chunk);
|
|
1327
|
-
} else if (bytes !== null) {
|
|
1328
|
-
done += bytes.byteLength;
|
|
1329
|
-
if (decoder.started) {
|
|
1330
|
-
text = filter.push(decoder.decode(bytes, true));
|
|
1331
|
-
} else {
|
|
1332
|
-
head.push(bytes);
|
|
1333
|
-
headLength += bytes.byteLength;
|
|
1334
|
-
text = headLength >= needed ? filter.push(flushHead(true)) : "";
|
|
1335
|
-
}
|
|
1336
|
-
} else {
|
|
1411
|
+
this.done += this.counting ? utf8Length(chunk) : 0;
|
|
1412
|
+
return this.filter.push(this.beforeText() + chunk);
|
|
1413
|
+
}
|
|
1414
|
+
const bytes = asBytes(chunk);
|
|
1415
|
+
if (bytes === null) {
|
|
1337
1416
|
throw new GraphFormatError("E_UNSUPPORTED", "an input chunk must be a string or a Uint8Array", {
|
|
1338
1417
|
reason: "chunk type",
|
|
1339
1418
|
found: typeof chunk,
|
|
1340
1419
|
});
|
|
1341
1420
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1421
|
+
this.done += bytes.byteLength;
|
|
1422
|
+
if (this.decoder.started) {
|
|
1423
|
+
return this.filter.push(this.decoder.decode(bytes, true));
|
|
1344
1424
|
}
|
|
1345
|
-
|
|
1425
|
+
this.head.push(bytes);
|
|
1426
|
+
this.headLength += bytes.byteLength;
|
|
1427
|
+
return this.headLength >= this.needed ? this.filter.push(this.flushHead(true)) : "";
|
|
1346
1428
|
}
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* The input ended: the filtered text of whatever is still held or pending.
|
|
1432
|
+
* @returns the text
|
|
1433
|
+
*/
|
|
1434
|
+
end(): string {
|
|
1435
|
+
let tail = "";
|
|
1436
|
+
if (this.headLength > 0) {
|
|
1437
|
+
tail = this.flushHead(false);
|
|
1438
|
+
} else if (this.decoder.started) {
|
|
1439
|
+
tail = this.decoder.decode(new Uint8Array(0), false);
|
|
1440
|
+
}
|
|
1441
|
+
return this.filter.push(tail);
|
|
1352
1442
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* Start the decoder on the held bytes and decode them.
|
|
1446
|
+
* @param stream - whether more bytes follow
|
|
1447
|
+
* @returns their text
|
|
1448
|
+
*/
|
|
1449
|
+
private flushHead(stream: boolean): string {
|
|
1450
|
+
const bytes = concatBytes(this.head);
|
|
1451
|
+
this.head = [];
|
|
1452
|
+
this.headLength = 0;
|
|
1453
|
+
this.decoder.start(bytes.subarray(0, this.headBytes));
|
|
1454
|
+
return this.decoder.decode(bytes, stream);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/**
|
|
1458
|
+
* A text chunk follows: finish any byte sequence still pending in the decoder before switching
|
|
1459
|
+
* to text. The BOM and declaration checks apply to the start of the input only, so bytes after
|
|
1460
|
+
* text are never taken for a BOM.
|
|
1461
|
+
* @returns the text of the pending bytes
|
|
1462
|
+
*/
|
|
1463
|
+
private beforeText(): string {
|
|
1464
|
+
if (this.headLength > 0) {
|
|
1465
|
+
return this.decoder.flushBeforeText(() => this.flushHead(false));
|
|
1466
|
+
}
|
|
1467
|
+
if (this.decoder.started) {
|
|
1468
|
+
return this.decoder.flushBeforeText(() => this.decoder.decode(new Uint8Array(0), false));
|
|
1469
|
+
}
|
|
1470
|
+
this.decoder.start(new Uint8Array(0));
|
|
1471
|
+
return "";
|
|
1356
1472
|
}
|
|
1357
|
-
filter.end();
|
|
1358
|
-
onProgress?.(done, done);
|
|
1359
1473
|
}
|
|
1360
1474
|
|
|
1361
1475
|
/**
|
|
@@ -1493,10 +1493,11 @@ async function* blockElements(
|
|
|
1493
1493
|
* @category Plugin helpers
|
|
1494
1494
|
*/
|
|
1495
1495
|
export function reportTooDeep(report: ImportReportBuilder, event: Extract<AspectEvent, { kind: "deep" }>): void {
|
|
1496
|
+
const what = event.aspect === null ? "a member of the document" : `an element of "${event.aspect}"`;
|
|
1496
1497
|
report.error(
|
|
1497
1498
|
"parse-error",
|
|
1498
1499
|
BAD_ASPECT_BLOCK_CODE,
|
|
1499
|
-
`${
|
|
1500
|
+
`${what} is nested ${event.depth} levels deep, more than the ${MAX_ELEMENT_DEPTH} a graph keeps; skipped`,
|
|
1500
1501
|
{ line: event.line, element: event.aspect },
|
|
1501
1502
|
);
|
|
1502
1503
|
}
|
|
@@ -1893,7 +1894,8 @@ export class CxStructure {
|
|
|
1893
1894
|
}
|
|
1894
1895
|
const error = typeof value.error === "string" ? value.error : "";
|
|
1895
1896
|
if (value.success === false) {
|
|
1896
|
-
const
|
|
1897
|
+
const detail = error.length > 0 ? `: ${error}` : "";
|
|
1898
|
+
const message = `the producer marked the document as failed${detail}`;
|
|
1897
1899
|
this.report.error("validation-error", STATUS_FAILED_CODE, message, { line, element: "status" });
|
|
1898
1900
|
throw this.report.abort(message, { code: STATUS_FAILED_CODE });
|
|
1899
1901
|
}
|
package/src/common/text.ts
CHANGED
|
@@ -202,21 +202,7 @@ export class TextCellWriter {
|
|
|
202
202
|
write(row: number, text: string): void {
|
|
203
203
|
const kind = inferTextDtype(text);
|
|
204
204
|
const value = parseTextCell(text);
|
|
205
|
-
|
|
206
|
-
typeof value === "number" &&
|
|
207
|
-
!Number.isSafeInteger(value) &&
|
|
208
|
-
I32_TEXT.test(text) &&
|
|
209
|
-
BigInt(text) !== BigInt(value)
|
|
210
|
-
) {
|
|
211
|
-
// an integer beyond 2^53 is stored as the nearest double; say so once per column
|
|
212
|
-
this.report.warnOnce(
|
|
213
|
-
"precision",
|
|
214
|
-
PRECISION_CODE,
|
|
215
|
-
`${this.domain} column "${this.name}": integers beyond 2^53 are stored as the nearest double (first: ${text})`,
|
|
216
|
-
{ element: this.name },
|
|
217
|
-
`${PRECISION_CODE}:${this.domain}:${this.name}`,
|
|
218
|
-
);
|
|
219
|
-
}
|
|
205
|
+
this.warnIfImprecise(text, value);
|
|
220
206
|
const wasString = this.textDtype === "string";
|
|
221
207
|
const textDtype =
|
|
222
208
|
this.textDtype === null || textDtypeRank(kind) > textDtypeRank(this.textDtype) ? kind : this.textDtype;
|
|
@@ -250,6 +236,29 @@ export class TextCellWriter {
|
|
|
250
236
|
}
|
|
251
237
|
}
|
|
252
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Warn, once per column, when the cell is an integer beyond 2^53 that is stored as the nearest
|
|
241
|
+
* double.
|
|
242
|
+
* @param text - the cell text
|
|
243
|
+
* @param value - the parsed value
|
|
244
|
+
*/
|
|
245
|
+
private warnIfImprecise(text: string, value: unknown): void {
|
|
246
|
+
if (
|
|
247
|
+
typeof value === "number" &&
|
|
248
|
+
!Number.isSafeInteger(value) &&
|
|
249
|
+
I32_TEXT.test(text) &&
|
|
250
|
+
BigInt(text) !== BigInt(value)
|
|
251
|
+
) {
|
|
252
|
+
this.report.warnOnce(
|
|
253
|
+
"precision",
|
|
254
|
+
PRECISION_CODE,
|
|
255
|
+
`${this.domain} column "${this.name}": integers beyond 2^53 are stored as the nearest double (first: ${text})`,
|
|
256
|
+
{ element: this.name },
|
|
257
|
+
`${PRECISION_CODE}:${this.domain}:${this.name}`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
253
262
|
/**
|
|
254
263
|
* Write a value (the parsed scalar, or a text into a widened column).
|
|
255
264
|
* @param row - the row
|
package/src/common/xml.ts
CHANGED
|
@@ -416,7 +416,8 @@ export function decodeEntities(raw: string, line: number, repairs?: XmlRepairs):
|
|
|
416
416
|
const digits = name.slice(hex ? 2 : 1);
|
|
417
417
|
// XML 1.0 puts no limit on leading zeros (A is "A")
|
|
418
418
|
const ok = hex ? /^0*[\da-fA-F]{1,6}$/.test(digits) : /^0*\d{1,7}$/.test(digits);
|
|
419
|
-
const
|
|
419
|
+
const radix = hex ? 16 : 10;
|
|
420
|
+
const cp = ok ? Number.parseInt(digits, radix) : -1;
|
|
420
421
|
const low = cp >= 0xd800 && cp <= 0xdbff ? lowSurrogateAfter(raw, semi + 1, repairs) : null;
|
|
421
422
|
if (low !== null) {
|
|
422
423
|
repairs?.surrogatePair?.(lineAt(raw, amp, line));
|
package/src/common/zip.ts
CHANGED
|
@@ -134,6 +134,52 @@ export function startsLikeZip(bytes: Uint8Array): boolean {
|
|
|
134
134
|
return bytes.byteLength >= 4 && bytes[0] === 0x50 && bytes[1] === 0x4b && bytes[2] === 3 && bytes[3] === 4;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Read the end of central directory record, following the zip64 locator when there is one.
|
|
139
|
+
* @param view - the whole archive
|
|
140
|
+
* @param eocd - the offset of the end record
|
|
141
|
+
* @returns the disk numbers, the entry count and where the central directory is; ZipError
|
|
142
|
+
* "corrupt" when the end record needs a zip64 record the archive does not have
|
|
143
|
+
*/
|
|
144
|
+
function readEndRecord(
|
|
145
|
+
view: DataView,
|
|
146
|
+
eocd: number,
|
|
147
|
+
): {
|
|
148
|
+
disk: number;
|
|
149
|
+
directoryDisk: number;
|
|
150
|
+
count: number;
|
|
151
|
+
directorySize: number;
|
|
152
|
+
directoryOffset: number;
|
|
153
|
+
directoryEnd: number;
|
|
154
|
+
} {
|
|
155
|
+
const locator = eocd - ZIP64_LOCATOR_SIZE;
|
|
156
|
+
if (locator >= 0 && view.getUint32(locator, true) === ZIP64_LOCATOR_SIGNATURE) {
|
|
157
|
+
const record = zip64EndRecord(view, locator);
|
|
158
|
+
return {
|
|
159
|
+
disk: view.getUint32(record + 16, true),
|
|
160
|
+
directoryDisk: view.getUint32(record + 20, true),
|
|
161
|
+
count: readU64(view, record + 32),
|
|
162
|
+
directorySize: readU64(view, record + 40),
|
|
163
|
+
directoryOffset: readU64(view, record + 48),
|
|
164
|
+
directoryEnd: record,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const count = view.getUint16(eocd + 10, true);
|
|
168
|
+
const directorySize = view.getUint32(eocd + 12, true);
|
|
169
|
+
const directoryOffset = view.getUint32(eocd + 16, true);
|
|
170
|
+
if (count === U16_MAX || directorySize === U32_MAX || directoryOffset === U32_MAX) {
|
|
171
|
+
throw new ZipError("corrupt", "the end record needs a zip64 record the archive does not have");
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
disk: view.getUint16(eocd + 4, true),
|
|
175
|
+
directoryDisk: view.getUint16(eocd + 6, true),
|
|
176
|
+
count,
|
|
177
|
+
directorySize,
|
|
178
|
+
directoryOffset,
|
|
179
|
+
directoryEnd: eocd,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
137
183
|
/**
|
|
138
184
|
* Read the central directory.
|
|
139
185
|
* @param bytes - the whole archive
|
|
@@ -150,24 +196,7 @@ export function readZipDirectory(bytes: Uint8Array): ZipEntry[] {
|
|
|
150
196
|
? new ZipError("corrupt", "the archive has no end of central directory record (truncated?)")
|
|
151
197
|
: new ZipError("not-zip", "the input is not a zip archive");
|
|
152
198
|
}
|
|
153
|
-
|
|
154
|
-
let directoryDisk = view.getUint16(eocd + 6, true);
|
|
155
|
-
let count = view.getUint16(eocd + 10, true);
|
|
156
|
-
let directorySize = view.getUint32(eocd + 12, true);
|
|
157
|
-
let directoryOffset = view.getUint32(eocd + 16, true);
|
|
158
|
-
let directoryEnd = eocd;
|
|
159
|
-
const locator = eocd - ZIP64_LOCATOR_SIZE;
|
|
160
|
-
if (locator >= 0 && view.getUint32(locator, true) === ZIP64_LOCATOR_SIGNATURE) {
|
|
161
|
-
const record = zip64EndRecord(view, locator);
|
|
162
|
-
disk = view.getUint32(record + 16, true);
|
|
163
|
-
directoryDisk = view.getUint32(record + 20, true);
|
|
164
|
-
count = readU64(view, record + 32);
|
|
165
|
-
directorySize = readU64(view, record + 40);
|
|
166
|
-
directoryOffset = readU64(view, record + 48);
|
|
167
|
-
directoryEnd = record;
|
|
168
|
-
} else if (count === U16_MAX || directorySize === U32_MAX || directoryOffset === U32_MAX) {
|
|
169
|
-
throw new ZipError("corrupt", "the end record needs a zip64 record the archive does not have");
|
|
170
|
-
}
|
|
199
|
+
const { disk, directoryDisk, count, directorySize, directoryOffset, directoryEnd } = readEndRecord(view, eocd);
|
|
171
200
|
if (disk !== 0 || directoryDisk !== 0) {
|
|
172
201
|
throw new ZipError("unsupported", "a split (multi-disk) archive cannot be read");
|
|
173
202
|
}
|