@graphty/graph-io 0.3.18 → 0.3.20
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 +80 -2
- package/dist/chunks/{escape-D-gZWO26.js → escape-B-tkk_Cl.js} +74 -16
- package/dist/chunks/escape-B-tkk_Cl.js.map +1 -0
- package/dist/chunks/{writer-GAdltGmC.js → export-Bh60Dv-n.js} +7 -252
- package/dist/chunks/export-Bh60Dv-n.js.map +1 -0
- package/dist/chunks/exporter-BGrsWamJ.js +736 -0
- package/dist/chunks/exporter-BGrsWamJ.js.map +1 -0
- package/dist/chunks/exporter-DIeGJXAZ.js +834 -0
- package/dist/chunks/exporter-DIeGJXAZ.js.map +1 -0
- package/dist/chunks/{importer-Br_QeAeE.js → importer-BWY2FFc5.js} +28 -6
- package/dist/chunks/importer-BWY2FFc5.js.map +1 -0
- package/dist/chunks/importer-BaQpCEcJ.js +2194 -0
- package/dist/chunks/importer-BaQpCEcJ.js.map +1 -0
- package/dist/chunks/importer-DD-xv9_Y.js +1471 -0
- package/dist/chunks/importer-DD-xv9_Y.js.map +1 -0
- package/dist/chunks/{importer-aNJfe0qu.js → importer-DEcKhsmQ.js} +2 -2
- package/dist/chunks/{importer-aNJfe0qu.js.map → importer-DEcKhsmQ.js.map} +1 -1
- package/dist/chunks/{importer-DHagxvDD.js → importer-DIrbbnAf.js} +6 -4
- package/dist/chunks/{importer-DHagxvDD.js.map → importer-DIrbbnAf.js.map} +1 -1
- package/dist/chunks/importer-D_e7e7LX.js +3055 -0
- package/dist/chunks/importer-D_e7e7LX.js.map +1 -0
- package/dist/chunks/{importer-Du5crN9l.js → importer-Wv4C4gaF.js} +278 -82
- package/dist/chunks/importer-Wv4C4gaF.js.map +1 -0
- package/dist/chunks/importer-vi3bSdtw.js +1713 -0
- package/dist/chunks/importer-vi3bSdtw.js.map +1 -0
- package/dist/chunks/{importer-d0uQxFp6.js → importer-zGLo8Dg_.js} +6 -4
- package/dist/chunks/{importer-d0uQxFp6.js.map → importer-zGLo8Dg_.js.map} +1 -1
- package/dist/chunks/json-elements-DDS8N2Dd.js +779 -0
- package/dist/chunks/json-elements-DDS8N2Dd.js.map +1 -0
- package/dist/chunks/{records-Bk9jgodz.js → records-dbRkxwaq.js} +2 -2
- package/dist/chunks/{records-Bk9jgodz.js.map → records-dbRkxwaq.js.map} +1 -1
- package/dist/chunks/{report-BOk0p5y8.js → report-B1z4WT9e.js} +143 -111
- package/dist/chunks/{report-BOk0p5y8.js.map → report-B1z4WT9e.js.map} +1 -1
- package/dist/chunks/weights-Dzba96G3.js +176 -0
- package/dist/chunks/weights-Dzba96G3.js.map +1 -0
- package/dist/chunks/writer-C7flM-Ih.js +77 -0
- package/dist/chunks/writer-C7flM-Ih.js.map +1 -0
- package/dist/csv.js +6 -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/cys.d.ts +1 -0
- package/dist/cys.js +6 -0
- package/dist/cys.js.map +1 -0
- package/dist/dot.js +1 -1
- package/dist/gexf.js +16 -4
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +13 -14
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +302 -399
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +1 -1
- package/dist/json.js +1 -1
- package/dist/neo4j.js +6 -4
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/cell-budget.d.ts +84 -0
- package/dist/src/common/cell-budget.d.ts.map +1 -0
- package/dist/src/common/cell-budget.js +138 -0
- package/dist/src/common/cell-budget.js.map +1 -0
- package/dist/src/common/input.d.ts +10 -0
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +39 -0
- package/dist/src/common/input.js.map +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/common/options.d.ts +6 -0
- package/dist/src/common/options.d.ts.map +1 -1
- package/dist/src/common/options.js +1 -1
- package/dist/src/common/options.js.map +1 -1
- package/dist/src/common/xml.d.ts +37 -3
- package/dist/src/common/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +86 -6
- package/dist/src/common/xml.js.map +1 -1
- package/dist/src/common/zip.d.ts +92 -0
- package/dist/src/common/zip.d.ts.map +1 -0
- package/dist/src/common/zip.js +399 -0
- package/dist/src/common/zip.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/cys/constants.d.ts +68 -0
- package/dist/src/formats/cys/constants.d.ts.map +1 -0
- package/dist/src/formats/cys/constants.js +76 -0
- package/dist/src/formats/cys/constants.js.map +1 -0
- package/dist/src/formats/cys/importer.d.ts +38 -0
- package/dist/src/formats/cys/importer.d.ts.map +1 -0
- package/dist/src/formats/cys/importer.js +830 -0
- package/dist/src/formats/cys/importer.js.map +1 -0
- package/dist/src/formats/cys/index.d.ts +7 -0
- package/dist/src/formats/cys/index.d.ts.map +1 -0
- package/dist/src/formats/cys/index.js +7 -0
- package/dist/src/formats/cys/index.js.map +1 -0
- package/dist/src/formats/cys/session.d.ts +132 -0
- package/dist/src/formats/cys/session.d.ts.map +1 -0
- package/dist/src/formats/cys/session.js +315 -0
- package/dist/src/formats/cys/session.js.map +1 -0
- package/dist/src/formats/cys/tables.d.ts +90 -0
- package/dist/src/formats/cys/tables.d.ts.map +1 -0
- package/dist/src/formats/cys/tables.js +293 -0
- package/dist/src/formats/cys/tables.js.map +1 -0
- package/dist/src/formats/dot/exporter.d.ts +2 -0
- package/dist/src/formats/dot/exporter.d.ts.map +1 -1
- package/dist/src/formats/dot/exporter.js +12 -0
- package/dist/src/formats/dot/exporter.js.map +1 -1
- package/dist/src/formats/dot/importer.js +6 -2
- package/dist/src/formats/dot/importer.js.map +1 -1
- 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 +0 -2
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +1 -3
- 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.d.ts +0 -8
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +8 -18
- 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/formats/xgmml/columns.d.ts +152 -0
- package/dist/src/formats/xgmml/columns.d.ts.map +1 -0
- package/dist/src/formats/xgmml/columns.js +593 -0
- package/dist/src/formats/xgmml/columns.js.map +1 -0
- package/dist/src/formats/xgmml/constants.d.ts +196 -0
- package/dist/src/formats/xgmml/constants.d.ts.map +1 -0
- package/dist/src/formats/xgmml/constants.js +197 -0
- package/dist/src/formats/xgmml/constants.js.map +1 -0
- package/dist/src/formats/xgmml/document.d.ts +361 -0
- package/dist/src/formats/xgmml/document.d.ts.map +1 -0
- package/dist/src/formats/xgmml/document.js +695 -0
- package/dist/src/formats/xgmml/document.js.map +1 -0
- package/dist/src/formats/xgmml/emit.d.ts +341 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -0
- package/dist/src/formats/xgmml/emit.js +1275 -0
- package/dist/src/formats/xgmml/emit.js.map +1 -0
- package/dist/src/formats/xgmml/exporter.d.ts +24 -0
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -0
- package/dist/src/formats/xgmml/exporter.js +999 -0
- package/dist/src/formats/xgmml/exporter.js.map +1 -0
- package/dist/src/formats/xgmml/importer.d.ts +58 -0
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -0
- package/dist/src/formats/xgmml/importer.js +360 -0
- package/dist/src/formats/xgmml/importer.js.map +1 -0
- package/dist/src/formats/xgmml/index.d.ts +8 -0
- package/dist/src/formats/xgmml/index.d.ts.map +1 -0
- package/dist/src/formats/xgmml/index.js +8 -0
- package/dist/src/formats/xgmml/index.js.map +1 -0
- package/dist/src/formats/xgmml/values.d.ts +60 -0
- package/dist/src/formats/xgmml/values.d.ts.map +1 -0
- package/dist/src/formats/xgmml/values.js +148 -0
- package/dist/src/formats/xgmml/values.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/registry.d.ts +7 -0
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +30 -10
- 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 +4 -0
- package/dist/src/sniff.js.map +1 -1
- package/dist/xgmml.d.ts +1 -0
- package/dist/xgmml.js +9 -0
- package/dist/xgmml.js.map +1 -0
- package/package.json +25 -2
- package/src/common/cell-budget.ts +170 -0
- package/src/common/input.ts +40 -0
- package/src/common/json-elements.ts +1147 -0
- package/src/common/options.ts +1 -1
- package/src/common/xml.ts +127 -6
- package/src/common/zip.ts +472 -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/cys/constants.ts +99 -0
- package/src/formats/cys/importer.ts +1100 -0
- package/src/formats/cys/index.ts +7 -0
- package/src/formats/cys/session.ts +428 -0
- package/src/formats/cys/tables.ts +400 -0
- package/src/formats/dot/exporter.ts +17 -0
- package/src/formats/dot/importer.ts +6 -2
- package/src/formats/gexf/exporter.ts +11 -0
- package/src/formats/gexf/importer.ts +1 -2
- package/src/formats/gexf/index.ts +1 -1
- package/src/formats/gml/exporter.ts +8 -19
- package/src/formats/json/importer.ts +6 -105
- package/src/formats/xgmml/columns.ts +747 -0
- package/src/formats/xgmml/constants.ts +265 -0
- package/src/formats/xgmml/document.ts +975 -0
- package/src/formats/xgmml/emit.ts +1563 -0
- package/src/formats/xgmml/exporter.ts +1215 -0
- package/src/formats/xgmml/importer.ts +491 -0
- package/src/formats/xgmml/index.ts +8 -0
- package/src/formats/xgmml/values.ts +183 -0
- package/src/index.ts +19 -0
- package/src/registry.ts +46 -15
- package/src/sniff.ts +18 -1
- package/dist/chunks/escape-D-gZWO26.js.map +0 -1
- package/dist/chunks/importer-Br_QeAeE.js.map +0 -1
- package/dist/chunks/importer-Du5crN9l.js.map +0 -1
- package/dist/chunks/writer-GAdltGmC.js.map +0 -1
|
@@ -0,0 +1,1471 @@
|
|
|
1
|
+
import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
|
|
2
|
+
import { A as AMBIGUOUS_GRAPH_NAME_CODE, G as GRAPH_NOT_FOUND_CODE, M as MULTIPLE_GRAPHS_CODE, N as NO_GRAPH_CODE, u as EMPTY_INPUT_CODE, j as DANGLING_REFERENCE_CODE, g as STYLES_NOT_IMPORTED_CODE, T as TOO_LARGE_CODE, aB as decodeEntryName, y as throwIfAborted, aY as readBytes, w as ImportReportBuilder, x as textChunks, ao as ImportError, F as reportSinkOptions, z as reportUnusedOptions, H as chooseGraph, v as resolveImportOptions, K as IdCoercer, L as declareResolved } from "./report-B1z4WT9e.js";
|
|
3
|
+
import { X as XGMML_ISSUE, r as resolveSettings, m as membersOf, k as isCyTrue, l as XgmmlEmitter, p as parseXgmml, n as dialectOf, i as XGMML_ORIGIN_NAMESPACE } from "./importer-D_e7e7LX.js";
|
|
4
|
+
import { t as tokenizeXml, f as xmlDeclaredEncoding, X as XmlSyntaxError } from "./escape-B-tkk_Cl.js";
|
|
5
|
+
import { C as CsvRecordReader } from "./records-dbRkxwaq.js";
|
|
6
|
+
const FORMAT = "cys";
|
|
7
|
+
const EXTENSIONS = Object.freeze([".cys"]);
|
|
8
|
+
const MIME_TYPES = Object.freeze(["application/zip"]);
|
|
9
|
+
const META_KEY = "cytoscape";
|
|
10
|
+
const CYTOSCAPE_NAMESPACE = "cytoscape";
|
|
11
|
+
const SELECTED_COLUMN = "cytoscape.selected";
|
|
12
|
+
const HIDDEN_COLUMN = "cytoscape.hidden";
|
|
13
|
+
const VIEW_POSITION_PREFIX = "position@";
|
|
14
|
+
const DEFAULT_MAX_UNCOMPRESSED = 2 * 1024 * 1024 * 1024;
|
|
15
|
+
const MAX_RATIO = 1e3;
|
|
16
|
+
const RELAYED = Object.fromEntries(
|
|
17
|
+
Object.values(XGMML_ISSUE).map((code) => [code.replace(/^[EW]_/, ""), code])
|
|
18
|
+
);
|
|
19
|
+
const CYS_ISSUE = Object.freeze({
|
|
20
|
+
...RELAYED,
|
|
21
|
+
/** The input is not a zip archive (or is text). */
|
|
22
|
+
NOT_ZIP: "E_CYS_NOT_ZIP",
|
|
23
|
+
/** The archive is damaged: no end record, offsets outside the file, a bad CRC, truncated data. */
|
|
24
|
+
CORRUPT: "E_CYS_CORRUPT",
|
|
25
|
+
/** A zip without a session marker (`<x.y.z>.version` or `cysession.xml`). */
|
|
26
|
+
NOT_SESSION: "E_CYS_NOT_SESSION",
|
|
27
|
+
/** A zip feature the reader does not support: encryption, a compression method, split archives. */
|
|
28
|
+
UNSUPPORTED: "E_CYS_UNSUPPORTED",
|
|
29
|
+
/** A session version graph-io cannot read: a major above 3, or the 2011 3.0 pre-release layout. */
|
|
30
|
+
VERSION: "E_CYS_VERSION",
|
|
31
|
+
/** A table or a virtual column that cannot be read at all; it is skipped. */
|
|
32
|
+
TABLE: "E_CYS_TABLE",
|
|
33
|
+
/** Table rows with too few or too many cells, a repeated key, or a key matching no element. */
|
|
34
|
+
TABLE_ROW: "W_CYS_TABLE_ROW",
|
|
35
|
+
/** A collapsed group's members are not in the network; they are recorded in meta.extra. */
|
|
36
|
+
COLLAPSED_GROUP: "W_CYS_COLLAPSED_GROUP",
|
|
37
|
+
/** Entries the importer does not read (apps, global tables, properties, images, thumbnails). */
|
|
38
|
+
ENTRY_SKIPPED: "W_CYS_ENTRY_SKIPPED",
|
|
39
|
+
/** Two entries with one name; the first is read. */
|
|
40
|
+
DUPLICATE_ENTRY: "W_CYS_DUPLICATE_ENTRY",
|
|
41
|
+
/** The archive inflates beyond maxUncompressedBytes, or an entry beyond the ratio limit. */
|
|
42
|
+
TOO_LARGE: TOO_LARGE_CODE,
|
|
43
|
+
/** The session's styles are not applied (issue #706). */
|
|
44
|
+
STYLES_NOT_IMPORTED: STYLES_NOT_IMPORTED_CODE,
|
|
45
|
+
/** A view, table or network the session names but does not hold. */
|
|
46
|
+
DANGLING_REFERENCE: DANGLING_REFERENCE_CODE,
|
|
47
|
+
/** The input is empty. */
|
|
48
|
+
EMPTY_INPUT: EMPTY_INPUT_CODE,
|
|
49
|
+
/** The session holds no network. */
|
|
50
|
+
NO_GRAPH: NO_GRAPH_CODE,
|
|
51
|
+
/** The session holds several networks; one was read. */
|
|
52
|
+
MULTIPLE_GRAPHS: MULTIPLE_GRAPHS_CODE,
|
|
53
|
+
/** graphIndex / graphName names no network (fatal). */
|
|
54
|
+
GRAPH_NOT_FOUND: GRAPH_NOT_FOUND_CODE,
|
|
55
|
+
/** graphName names several networks (fatal). */
|
|
56
|
+
AMBIGUOUS_GRAPH_NAME: AMBIGUOUS_GRAPH_NAME_CODE
|
|
57
|
+
});
|
|
58
|
+
class ZipError extends Error {
|
|
59
|
+
/**
|
|
60
|
+
* Create the error.
|
|
61
|
+
* @param kind - what went wrong
|
|
62
|
+
* @param message - a plain-ASCII message
|
|
63
|
+
*/
|
|
64
|
+
constructor(kind, message) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.name = "ZipError";
|
|
67
|
+
this.kind = kind;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const RATIO_FLOOR = 1024 * 1024;
|
|
71
|
+
const EOCD_SIGNATURE = 101010256;
|
|
72
|
+
const ZIP64_LOCATOR_SIGNATURE = 117853008;
|
|
73
|
+
const ZIP64_EOCD_SIGNATURE = 101075792;
|
|
74
|
+
const CENTRAL_SIGNATURE = 33639248;
|
|
75
|
+
const LOCAL_SIGNATURE = 67324752;
|
|
76
|
+
const EOCD_SIZE = 22;
|
|
77
|
+
const MAX_COMMENT = 65535;
|
|
78
|
+
const CENTRAL_SIZE = 46;
|
|
79
|
+
const LOCAL_SIZE = 30;
|
|
80
|
+
const ZIP64_EOCD_SIZE = 56;
|
|
81
|
+
const ZIP64_LOCATOR_SIZE = 20;
|
|
82
|
+
const U16_MAX = 65535;
|
|
83
|
+
const U32_MAX = 4294967295;
|
|
84
|
+
const CRC_SLICE = 1024 * 1024;
|
|
85
|
+
const METHOD_NAMES = {
|
|
86
|
+
1: "shrink",
|
|
87
|
+
6: "implode",
|
|
88
|
+
9: "deflate64",
|
|
89
|
+
12: "bzip2",
|
|
90
|
+
14: "LZMA",
|
|
91
|
+
93: "zstd",
|
|
92
|
+
95: "xz",
|
|
93
|
+
96: "JPEG",
|
|
94
|
+
97: "WavPack",
|
|
95
|
+
98: "PPMd",
|
|
96
|
+
99: "AES encryption"
|
|
97
|
+
};
|
|
98
|
+
function startsLikeZip(bytes) {
|
|
99
|
+
return bytes.byteLength >= 4 && bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4;
|
|
100
|
+
}
|
|
101
|
+
function readZipDirectory(bytes) {
|
|
102
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
103
|
+
const eocd = findEndRecord(bytes, view);
|
|
104
|
+
if (eocd < 0) {
|
|
105
|
+
throw startsLikeZip(bytes) ? new ZipError("corrupt", "the archive has no end of central directory record (truncated?)") : new ZipError("not-zip", "the input is not a zip archive");
|
|
106
|
+
}
|
|
107
|
+
let disk = view.getUint16(eocd + 4, true);
|
|
108
|
+
let directoryDisk = view.getUint16(eocd + 6, true);
|
|
109
|
+
let count = view.getUint16(eocd + 10, true);
|
|
110
|
+
let directorySize = view.getUint32(eocd + 12, true);
|
|
111
|
+
let directoryOffset = view.getUint32(eocd + 16, true);
|
|
112
|
+
let directoryEnd = eocd;
|
|
113
|
+
const locator = eocd - ZIP64_LOCATOR_SIZE;
|
|
114
|
+
if (locator >= 0 && view.getUint32(locator, true) === ZIP64_LOCATOR_SIGNATURE) {
|
|
115
|
+
const record = zip64EndRecord(view, locator);
|
|
116
|
+
disk = view.getUint32(record + 16, true);
|
|
117
|
+
directoryDisk = view.getUint32(record + 20, true);
|
|
118
|
+
count = readU64(view, record + 32);
|
|
119
|
+
directorySize = readU64(view, record + 40);
|
|
120
|
+
directoryOffset = readU64(view, record + 48);
|
|
121
|
+
directoryEnd = record;
|
|
122
|
+
} else if (count === U16_MAX || directorySize === U32_MAX || directoryOffset === U32_MAX) {
|
|
123
|
+
throw new ZipError("corrupt", "the end record needs a zip64 record the archive does not have");
|
|
124
|
+
}
|
|
125
|
+
if (disk !== 0 || directoryDisk !== 0) {
|
|
126
|
+
throw new ZipError("unsupported", "a split (multi-disk) archive cannot be read");
|
|
127
|
+
}
|
|
128
|
+
const start = directoryEnd - directorySize;
|
|
129
|
+
if (start < 0 || directoryOffset > bytes.byteLength) {
|
|
130
|
+
throw new ZipError("corrupt", "the central directory lies outside the archive");
|
|
131
|
+
}
|
|
132
|
+
const shift = start - directoryOffset;
|
|
133
|
+
if (shift < 0) {
|
|
134
|
+
throw new ZipError("corrupt", "the central directory offset does not match its position");
|
|
135
|
+
}
|
|
136
|
+
const entries = [];
|
|
137
|
+
let at = start;
|
|
138
|
+
for (let i = 0; i < count; i++) {
|
|
139
|
+
if (at + CENTRAL_SIZE > directoryEnd || view.getUint32(at, true) !== CENTRAL_SIGNATURE) {
|
|
140
|
+
throw new ZipError("corrupt", `central directory entry ${i + 1} of ${count} is damaged`);
|
|
141
|
+
}
|
|
142
|
+
const nameLength = view.getUint16(at + 28, true);
|
|
143
|
+
const extraLength = view.getUint16(at + 30, true);
|
|
144
|
+
const commentLength = view.getUint16(at + 32, true);
|
|
145
|
+
const next = at + CENTRAL_SIZE + nameLength + extraLength + commentLength;
|
|
146
|
+
if (next > directoryEnd) {
|
|
147
|
+
throw new ZipError("corrupt", `central directory entry ${i + 1} runs past the directory`);
|
|
148
|
+
}
|
|
149
|
+
const name = decodeEntryName(bytes.subarray(at + CENTRAL_SIZE, at + CENTRAL_SIZE + nameLength));
|
|
150
|
+
let compressedSize = view.getUint32(at + 20, true);
|
|
151
|
+
let size = view.getUint32(at + 24, true);
|
|
152
|
+
let localOffset = view.getUint32(at + 42, true);
|
|
153
|
+
if (compressedSize === U32_MAX || size === U32_MAX || localOffset === U32_MAX) {
|
|
154
|
+
({ compressedSize, size, localOffset } = zip64Extra(view, at + CENTRAL_SIZE + nameLength, extraLength, {
|
|
155
|
+
compressedSize,
|
|
156
|
+
size,
|
|
157
|
+
localOffset
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
entries.push(
|
|
161
|
+
Object.freeze({
|
|
162
|
+
name,
|
|
163
|
+
method: view.getUint16(at + 10, true),
|
|
164
|
+
flags: view.getUint16(at + 8, true),
|
|
165
|
+
crc32: view.getUint32(at + 16, true),
|
|
166
|
+
compressedSize,
|
|
167
|
+
size,
|
|
168
|
+
localOffset: localOffset + shift,
|
|
169
|
+
directory: name.endsWith("/")
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
at = next;
|
|
173
|
+
}
|
|
174
|
+
return entries;
|
|
175
|
+
}
|
|
176
|
+
function findEndRecord(bytes, view) {
|
|
177
|
+
const lowest = Math.max(0, bytes.byteLength - EOCD_SIZE - MAX_COMMENT);
|
|
178
|
+
let fallback = -1;
|
|
179
|
+
for (let at = bytes.byteLength - EOCD_SIZE; at >= lowest; at--) {
|
|
180
|
+
if (view.getUint32(at, true) !== EOCD_SIGNATURE) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (at + EOCD_SIZE + view.getUint16(at + 20, true) === bytes.byteLength) {
|
|
184
|
+
return at;
|
|
185
|
+
}
|
|
186
|
+
if (fallback < 0) {
|
|
187
|
+
fallback = at;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return fallback;
|
|
191
|
+
}
|
|
192
|
+
function zip64EndRecord(view, locator) {
|
|
193
|
+
const stated = readU64(view, locator + 8);
|
|
194
|
+
for (const at of [stated, locator - ZIP64_EOCD_SIZE]) {
|
|
195
|
+
if (at >= 0 && at + ZIP64_EOCD_SIZE <= locator && view.getUint32(at, true) === ZIP64_EOCD_SIGNATURE) {
|
|
196
|
+
return at;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
throw new ZipError("corrupt", "the zip64 end record the locator names is missing");
|
|
200
|
+
}
|
|
201
|
+
function zip64Extra(view, at, length, fields) {
|
|
202
|
+
const out = { ...fields };
|
|
203
|
+
for (let p = at; p + 4 <= at + length; ) {
|
|
204
|
+
const id = view.getUint16(p, true);
|
|
205
|
+
const dataLength = view.getUint16(p + 2, true);
|
|
206
|
+
if (id === 1) {
|
|
207
|
+
let q = p + 4;
|
|
208
|
+
const end = q + dataLength;
|
|
209
|
+
for (const key of ["size", "compressedSize", "localOffset"]) {
|
|
210
|
+
if (fields[key] === U32_MAX) {
|
|
211
|
+
if (q + 8 > end) {
|
|
212
|
+
throw new ZipError("corrupt", "a zip64 extra field is too short");
|
|
213
|
+
}
|
|
214
|
+
out[key] = readU64(view, q);
|
|
215
|
+
q += 8;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
p += 4 + dataLength;
|
|
221
|
+
}
|
|
222
|
+
throw new ZipError("corrupt", "an entry needs a zip64 extra field it does not have");
|
|
223
|
+
}
|
|
224
|
+
function readU64(view, at) {
|
|
225
|
+
const high = view.getUint32(at + 4, true);
|
|
226
|
+
if (high >= 2097152) {
|
|
227
|
+
throw new ZipError("unsupported", "a zip64 size or offset beyond 2^53 cannot be read");
|
|
228
|
+
}
|
|
229
|
+
return high * 4294967296 + view.getUint32(at, true);
|
|
230
|
+
}
|
|
231
|
+
async function readZipEntry(bytes, entry, options) {
|
|
232
|
+
if ((entry.flags & 1) !== 0 || entry.method === 99) {
|
|
233
|
+
throw new ZipError("unsupported", `${entry.name}: the entry is encrypted`);
|
|
234
|
+
}
|
|
235
|
+
if (entry.method !== 0 && entry.method !== 8) {
|
|
236
|
+
const name = METHOD_NAMES[entry.method] ?? `method ${entry.method}`;
|
|
237
|
+
throw new ZipError(
|
|
238
|
+
"unsupported",
|
|
239
|
+
`${entry.name}: compression ${name} is not supported (only stored and deflate)`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if (entry.size > options.maxBytes) {
|
|
243
|
+
throw new ZipError(
|
|
244
|
+
"too-large",
|
|
245
|
+
`${entry.name}: ${entry.size} uncompressed bytes exceed the limit of ${options.maxBytes}`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (entry.size > RATIO_FLOOR && entry.size > options.maxRatio * Math.max(entry.compressedSize, 1)) {
|
|
249
|
+
throw new ZipError(
|
|
250
|
+
"too-large",
|
|
251
|
+
`${entry.name}: ${entry.size} bytes from ${entry.compressedSize} is a compression ratio above ${options.maxRatio}:1`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
const data = entryData(bytes, entry);
|
|
255
|
+
if (entry.method === 0) {
|
|
256
|
+
if (data.byteLength !== entry.size) {
|
|
257
|
+
throw new ZipError("corrupt", `${entry.name}: a stored entry's sizes disagree`);
|
|
258
|
+
}
|
|
259
|
+
let crc = 4294967295;
|
|
260
|
+
for (let at = 0; at < data.byteLength; at += CRC_SLICE) {
|
|
261
|
+
throwIfAborted(options.signal);
|
|
262
|
+
crc = crcUpdate(crc, data.subarray(at, Math.min(at + CRC_SLICE, data.byteLength)));
|
|
263
|
+
options.onBytes?.(Math.min(at + CRC_SLICE, data.byteLength));
|
|
264
|
+
}
|
|
265
|
+
if ((crc ^ 4294967295) >>> 0 !== entry.crc32) {
|
|
266
|
+
throw new ZipError("corrupt", `${entry.name}: the data does not match its CRC-32`);
|
|
267
|
+
}
|
|
268
|
+
return data;
|
|
269
|
+
}
|
|
270
|
+
return inflate(data, entry, options);
|
|
271
|
+
}
|
|
272
|
+
function entryData(bytes, entry) {
|
|
273
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
274
|
+
const at = entry.localOffset;
|
|
275
|
+
if (at + LOCAL_SIZE > bytes.byteLength || view.getUint32(at, true) !== LOCAL_SIGNATURE) {
|
|
276
|
+
throw new ZipError("corrupt", `${entry.name}: the local header is missing`);
|
|
277
|
+
}
|
|
278
|
+
const start = at + LOCAL_SIZE + view.getUint16(at + 26, true) + view.getUint16(at + 28, true);
|
|
279
|
+
const end = start + entry.compressedSize;
|
|
280
|
+
if (end > bytes.byteLength) {
|
|
281
|
+
throw new ZipError("corrupt", `${entry.name}: the entry's data runs past the end of the archive (truncated?)`);
|
|
282
|
+
}
|
|
283
|
+
return bytes.subarray(start, end);
|
|
284
|
+
}
|
|
285
|
+
async function inflate(data, entry, options) {
|
|
286
|
+
if (typeof DecompressionStream !== "function") {
|
|
287
|
+
throw new ZipError(
|
|
288
|
+
"unsupported",
|
|
289
|
+
"this runtime has no DecompressionStream, so deflated entries cannot be read"
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
const member = new Uint8Array(10 + data.byteLength + 8);
|
|
293
|
+
member.set([31, 139, 8, 0, 0, 0, 0, 0, 0, 255]);
|
|
294
|
+
member.set(data, 10);
|
|
295
|
+
const trailer = new DataView(member.buffer, 10 + data.byteLength, 8);
|
|
296
|
+
trailer.setUint32(0, entry.crc32, true);
|
|
297
|
+
trailer.setUint32(4, entry.size % 4294967296, true);
|
|
298
|
+
const source = new ReadableStream({
|
|
299
|
+
start(controller) {
|
|
300
|
+
controller.enqueue(member);
|
|
301
|
+
controller.close();
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
const reader = source.pipeThrough(new DecompressionStream("gzip")).getReader();
|
|
305
|
+
const parts = [];
|
|
306
|
+
let total = 0;
|
|
307
|
+
try {
|
|
308
|
+
for (; ; ) {
|
|
309
|
+
throwIfAborted(options.signal);
|
|
310
|
+
let chunk;
|
|
311
|
+
try {
|
|
312
|
+
chunk = await reader.read();
|
|
313
|
+
} catch {
|
|
314
|
+
throw new ZipError(
|
|
315
|
+
"corrupt",
|
|
316
|
+
`${entry.name}: the deflate data is damaged or does not match its CRC-32 and size`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
if (chunk.done) {
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
total += chunk.value.byteLength;
|
|
323
|
+
if (total > entry.size) {
|
|
324
|
+
throw new ZipError("corrupt", `${entry.name}: the data is longer than its directory size`);
|
|
325
|
+
}
|
|
326
|
+
parts.push(chunk.value);
|
|
327
|
+
options.onBytes?.(total);
|
|
328
|
+
}
|
|
329
|
+
} finally {
|
|
330
|
+
await reader.cancel().catch(() => void 0);
|
|
331
|
+
}
|
|
332
|
+
if (total !== entry.size) {
|
|
333
|
+
throw new ZipError("corrupt", `${entry.name}: the data is shorter than its directory size`);
|
|
334
|
+
}
|
|
335
|
+
if (parts.length === 1) {
|
|
336
|
+
return parts[0];
|
|
337
|
+
}
|
|
338
|
+
const out = new Uint8Array(total);
|
|
339
|
+
let at = 0;
|
|
340
|
+
for (const part of parts) {
|
|
341
|
+
out.set(part, at);
|
|
342
|
+
at += part.byteLength;
|
|
343
|
+
}
|
|
344
|
+
return out;
|
|
345
|
+
}
|
|
346
|
+
const CRC_TABLE = (() => {
|
|
347
|
+
const table = new Uint32Array(256);
|
|
348
|
+
for (let n = 0; n < 256; n++) {
|
|
349
|
+
let c = n;
|
|
350
|
+
for (let k = 0; k < 8; k++) {
|
|
351
|
+
c = (c & 1) === 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
352
|
+
}
|
|
353
|
+
table[n] = c >>> 0;
|
|
354
|
+
}
|
|
355
|
+
return table;
|
|
356
|
+
})();
|
|
357
|
+
function crcUpdate(crc, bytes) {
|
|
358
|
+
let c = crc;
|
|
359
|
+
for (const byte of bytes) {
|
|
360
|
+
c = CRC_TABLE[(c ^ byte) & 255] ^ c >>> 8;
|
|
361
|
+
}
|
|
362
|
+
return c >>> 0;
|
|
363
|
+
}
|
|
364
|
+
function urlDecode(text) {
|
|
365
|
+
const plus = text.replace(/\+/g, " ");
|
|
366
|
+
try {
|
|
367
|
+
return decodeURIComponent(plus);
|
|
368
|
+
} catch {
|
|
369
|
+
return plus;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function openSession(input, report, common, maxBytes) {
|
|
373
|
+
const bytes = await readBytes(input, common);
|
|
374
|
+
if (bytes === null) {
|
|
375
|
+
report.fail(
|
|
376
|
+
CYS_ISSUE.NOT_ZIP,
|
|
377
|
+
"a Cytoscape session is a zip archive; text input cannot hold one (pass the bytes)"
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
if (bytes.byteLength === 0) {
|
|
381
|
+
report.fail(CYS_ISSUE.EMPTY_INPUT, "the input is empty");
|
|
382
|
+
}
|
|
383
|
+
let zipEntries;
|
|
384
|
+
try {
|
|
385
|
+
zipEntries = readZipDirectory(bytes);
|
|
386
|
+
} catch (err) {
|
|
387
|
+
return zipFailure(err, report);
|
|
388
|
+
}
|
|
389
|
+
const layout = layoutOf(zipEntries, report);
|
|
390
|
+
let budget = maxBytes;
|
|
391
|
+
let inflated = 0;
|
|
392
|
+
const total = zipEntries.reduce((sum, e) => sum + e.size, 0);
|
|
393
|
+
return {
|
|
394
|
+
layout,
|
|
395
|
+
read: async (entry) => {
|
|
396
|
+
const before = inflated;
|
|
397
|
+
try {
|
|
398
|
+
const data = await readZipEntry(bytes, entry.zip, {
|
|
399
|
+
signal: common.signal,
|
|
400
|
+
maxBytes: budget,
|
|
401
|
+
maxRatio: MAX_RATIO,
|
|
402
|
+
onBytes: (n) => common.onProgress?.(before + n, total)
|
|
403
|
+
});
|
|
404
|
+
budget -= data.byteLength;
|
|
405
|
+
inflated += data.byteLength;
|
|
406
|
+
return data;
|
|
407
|
+
} catch (err) {
|
|
408
|
+
return zipFailure(err, report);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function zipFailure(err, report) {
|
|
414
|
+
if (!(err instanceof ZipError)) {
|
|
415
|
+
throw err;
|
|
416
|
+
}
|
|
417
|
+
const code = {
|
|
418
|
+
"not-zip": CYS_ISSUE.NOT_ZIP,
|
|
419
|
+
corrupt: CYS_ISSUE.CORRUPT,
|
|
420
|
+
unsupported: CYS_ISSUE.UNSUPPORTED,
|
|
421
|
+
"too-large": CYS_ISSUE.TOO_LARGE
|
|
422
|
+
}[err.kind];
|
|
423
|
+
if (err.kind === "too-large") {
|
|
424
|
+
report.error("unsupported", code, err.message);
|
|
425
|
+
throw report.abort(err.message, { code });
|
|
426
|
+
}
|
|
427
|
+
report.fail(code, err.message);
|
|
428
|
+
}
|
|
429
|
+
const NETWORK_FILE = /^networks\/(\d+)(?:-([^/]*))?\.xgmml$/;
|
|
430
|
+
const VIEW_FILE = /^views\/(\d+)-(\d+)(?:-[^/]*)?\.xgmml$/;
|
|
431
|
+
const TABLE_FILE = /^tables\/((\d+)(?:-[^/]*)?\/([^/-]+)-([^/-]+)-[^/]*\.cytable)$/;
|
|
432
|
+
const NOISE = /(^|\/)(__MACOSX\/|\.DS_Store$)/;
|
|
433
|
+
function layoutOf(zipEntries, report) {
|
|
434
|
+
const byName = /* @__PURE__ */ new Map();
|
|
435
|
+
const repeated = [];
|
|
436
|
+
for (const entry of zipEntries) {
|
|
437
|
+
if (entry.directory || NOISE.test(entry.name)) {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (byName.has(entry.name)) {
|
|
441
|
+
repeated.push(entry.name);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
byName.set(entry.name, entry);
|
|
445
|
+
}
|
|
446
|
+
if (repeated.length > 0) {
|
|
447
|
+
report.warning(
|
|
448
|
+
"unsupported",
|
|
449
|
+
CYS_ISSUE.DUPLICATE_ENTRY,
|
|
450
|
+
`${repeated.length} entry name(s) appear more than once; the first of each is read: ${listed(repeated)}`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
const names = [...byName.keys()];
|
|
454
|
+
const marker = names.find((n) => /^([^/]*\/)?[^/]+\.version$/.test(n));
|
|
455
|
+
const cysession = names.find((n) => /^([^/]*\/)?cysession\.xml$/.test(n));
|
|
456
|
+
if (marker === void 0 && cysession === void 0) {
|
|
457
|
+
report.fail(
|
|
458
|
+
CYS_ISSUE.NOT_SESSION,
|
|
459
|
+
"the zip holds neither a session version marker (<x.y.z>.version) nor cysession.xml: it is not a Cytoscape session"
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
const anchor = marker ?? cysession;
|
|
463
|
+
const root = anchor.slice(0, anchor.lastIndexOf("/") + 1);
|
|
464
|
+
let era = "2";
|
|
465
|
+
let version = "2.0.0";
|
|
466
|
+
if (marker !== void 0) {
|
|
467
|
+
version = marker.slice(root.length, -".version".length);
|
|
468
|
+
const major = Number(version.split(".")[0]);
|
|
469
|
+
if (!Number.isInteger(major) || major > 3) {
|
|
470
|
+
report.fail(
|
|
471
|
+
CYS_ISSUE.VERSION,
|
|
472
|
+
`the session version ${version} is newer than the Cytoscape 3 sessions graph-io reads`
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
era = major === 3 ? "3" : "2";
|
|
476
|
+
}
|
|
477
|
+
const layout = {
|
|
478
|
+
era,
|
|
479
|
+
version,
|
|
480
|
+
root,
|
|
481
|
+
networks: [],
|
|
482
|
+
views: [],
|
|
483
|
+
tables: [],
|
|
484
|
+
cytables: null,
|
|
485
|
+
networkList: null,
|
|
486
|
+
cysession: null,
|
|
487
|
+
files: /* @__PURE__ */ new Map(),
|
|
488
|
+
styles: [],
|
|
489
|
+
skipped: []
|
|
490
|
+
};
|
|
491
|
+
for (const [name, zip] of byName) {
|
|
492
|
+
if (!name.startsWith(root) || name === marker) {
|
|
493
|
+
if (name !== marker) {
|
|
494
|
+
layout.skipped.push(name);
|
|
495
|
+
}
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const path = name.slice(root.length);
|
|
499
|
+
const entry = { path, name, zip };
|
|
500
|
+
if (era === "3") {
|
|
501
|
+
classify3(entry, layout);
|
|
502
|
+
} else {
|
|
503
|
+
classify2(entry, layout);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return layout;
|
|
507
|
+
}
|
|
508
|
+
function classify3(entry, layout) {
|
|
509
|
+
const { path } = entry;
|
|
510
|
+
const network = NETWORK_FILE.exec(path);
|
|
511
|
+
const view = VIEW_FILE.exec(path);
|
|
512
|
+
const table = TABLE_FILE.exec(path);
|
|
513
|
+
if (network !== null) {
|
|
514
|
+
layout.networks.push({ ...entry, suid: network[1], title: urlDecode(network[2] ?? "") });
|
|
515
|
+
} else if (view !== null) {
|
|
516
|
+
layout.views.push({ ...entry, network: view[1], view: view[2] });
|
|
517
|
+
} else if (path === "tables/cytables.xml") {
|
|
518
|
+
layout.cytables = entry;
|
|
519
|
+
} else if (table !== null) {
|
|
520
|
+
const element = {
|
|
521
|
+
"org.cytoscape.model.CyNode": "node",
|
|
522
|
+
"org.cytoscape.model.CyEdge": "edge",
|
|
523
|
+
"org.cytoscape.model.CyNetwork": "network"
|
|
524
|
+
}[urlDecode(table[4])];
|
|
525
|
+
layout.tables.push({
|
|
526
|
+
...entry,
|
|
527
|
+
tablePath: table[1],
|
|
528
|
+
network: table[2],
|
|
529
|
+
namespace: urlDecode(table[3]),
|
|
530
|
+
element: element ?? null
|
|
531
|
+
});
|
|
532
|
+
} else if (path === "apps/org.cytoscape.swing-application/network_list.xml") {
|
|
533
|
+
layout.networkList = entry;
|
|
534
|
+
} else if (/(^|\/)session_vizmap\.(xml|props)$/.test(path)) {
|
|
535
|
+
layout.styles.push(entry);
|
|
536
|
+
} else {
|
|
537
|
+
layout.skipped.push(entry.name);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function classify2(entry, layout) {
|
|
541
|
+
const { path } = entry;
|
|
542
|
+
if (path === "cysession.xml") {
|
|
543
|
+
layout.cysession = entry;
|
|
544
|
+
} else if (/^[^/]+\.xgmml$/.test(path)) {
|
|
545
|
+
layout.files.set(path, entry);
|
|
546
|
+
} else if (/^session_vizmap\.(props|xml)$/.test(path)) {
|
|
547
|
+
layout.styles.push(entry);
|
|
548
|
+
} else {
|
|
549
|
+
layout.skipped.push(entry.name);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
function listed(names) {
|
|
553
|
+
const head = names.slice(0, 10).join(", ");
|
|
554
|
+
return names.length > 10 ? `${head} and ${names.length - 10} more` : head;
|
|
555
|
+
}
|
|
556
|
+
async function readXmlTree(bytes, entry, report, common) {
|
|
557
|
+
const scratch = new ImportReportBuilder(FORMAT, 0);
|
|
558
|
+
const stack = [{ name: "", attrs: /* @__PURE__ */ new Map(), children: [] }];
|
|
559
|
+
try {
|
|
560
|
+
await tokenizeXml(
|
|
561
|
+
textChunks(bytes, scratch, { signal: common.signal, declaredEncoding: xmlDeclaredEncoding }),
|
|
562
|
+
{
|
|
563
|
+
start(name, attrs) {
|
|
564
|
+
const node = { name: name.slice(name.lastIndexOf(":") + 1), attrs, children: [] };
|
|
565
|
+
stack[stack.length - 1].children.push(node);
|
|
566
|
+
stack.push(node);
|
|
567
|
+
},
|
|
568
|
+
end() {
|
|
569
|
+
stack.pop();
|
|
570
|
+
},
|
|
571
|
+
text() {
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
);
|
|
575
|
+
} catch (err) {
|
|
576
|
+
if (err instanceof XmlSyntaxError || err instanceof ImportError) {
|
|
577
|
+
report.fail(CYS_ISSUE.CORRUPT, `${entry}: ${err.message}`);
|
|
578
|
+
}
|
|
579
|
+
throw err;
|
|
580
|
+
}
|
|
581
|
+
const [root] = stack[0].children;
|
|
582
|
+
if (root === void 0) {
|
|
583
|
+
report.fail(CYS_ISSUE.CORRUPT, `${entry}: the document is empty`);
|
|
584
|
+
}
|
|
585
|
+
return root;
|
|
586
|
+
}
|
|
587
|
+
function elementsNamed(node, name) {
|
|
588
|
+
const out = [];
|
|
589
|
+
const stack = [node];
|
|
590
|
+
while (stack.length > 0) {
|
|
591
|
+
const n = stack.pop();
|
|
592
|
+
if (n.name === name) {
|
|
593
|
+
out.push(n);
|
|
594
|
+
}
|
|
595
|
+
for (let i = n.children.length - 1; i >= 0; i--) {
|
|
596
|
+
stack.push(n.children[i]);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return out;
|
|
600
|
+
}
|
|
601
|
+
const JAVA_TYPES = {
|
|
602
|
+
"java.lang.String": "String",
|
|
603
|
+
"java.lang.Long": "Long",
|
|
604
|
+
"java.lang.Integer": "Integer",
|
|
605
|
+
"java.lang.Double": "Double",
|
|
606
|
+
"java.lang.Boolean": "Boolean"
|
|
607
|
+
};
|
|
608
|
+
const LIST_CLASS = /^java\.util\.List<\s*([\w.]+)\s*>$/;
|
|
609
|
+
async function readCyTable(bytes, path, entry, report, common) {
|
|
610
|
+
const scratch = new ImportReportBuilder(FORMAT, 0);
|
|
611
|
+
const reader = new CsvRecordReader(bytes, scratch, { delimiter: ",", encoding: "utf-8", signal: common.signal });
|
|
612
|
+
const records = [];
|
|
613
|
+
try {
|
|
614
|
+
for await (const cells of reader) {
|
|
615
|
+
records.push({ cells, line: reader.line });
|
|
616
|
+
}
|
|
617
|
+
} catch (err) {
|
|
618
|
+
if (err instanceof ImportError) {
|
|
619
|
+
return tableError(report, entry, scratch.issues.at(-1)?.message ?? err.message);
|
|
620
|
+
}
|
|
621
|
+
throw err;
|
|
622
|
+
}
|
|
623
|
+
let at = 0;
|
|
624
|
+
let version = 0;
|
|
625
|
+
if (records[0]?.cells.length === 2 && records[0].cells[0] === "CyCSV-Version") {
|
|
626
|
+
version = Number(records[0].cells[1]);
|
|
627
|
+
at = 1;
|
|
628
|
+
}
|
|
629
|
+
if (version !== 0 && version !== 1) {
|
|
630
|
+
return tableError(report, entry, `CyCSV version "${records[0].cells[1]}" is not 0 or 1`);
|
|
631
|
+
}
|
|
632
|
+
const headerLines = version === 1 ? 4 : 3;
|
|
633
|
+
if (records.length < at + headerLines) {
|
|
634
|
+
return tableError(report, entry, "the table header is incomplete");
|
|
635
|
+
}
|
|
636
|
+
const names = records[at].cells;
|
|
637
|
+
const classes = records[at + 1].cells;
|
|
638
|
+
if (names.length === 0 || classes.length !== names.length) {
|
|
639
|
+
return tableError(report, entry, `${names.length} column name(s) but ${classes.length} column class(es)`);
|
|
640
|
+
}
|
|
641
|
+
const columns = [];
|
|
642
|
+
for (let i = 0; i < names.length; i++) {
|
|
643
|
+
const column = columnOf(names[i], classes[i]);
|
|
644
|
+
if (column === null) {
|
|
645
|
+
return tableError(
|
|
646
|
+
report,
|
|
647
|
+
entry,
|
|
648
|
+
`column "${names[i]}" has the class "${classes[i]}", which graph-io cannot read`
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
columns.push(column);
|
|
652
|
+
}
|
|
653
|
+
const rows = /* @__PURE__ */ new Map();
|
|
654
|
+
const lines = /* @__PURE__ */ new Map();
|
|
655
|
+
const problems = { short: 0, long: 0, repeated: 0 };
|
|
656
|
+
for (const record of records.slice(at + headerLines)) {
|
|
657
|
+
const { cells } = record;
|
|
658
|
+
if (cells.length < columns.length) {
|
|
659
|
+
problems.short++;
|
|
660
|
+
} else if (cells.length > columns.length) {
|
|
661
|
+
problems.long++;
|
|
662
|
+
}
|
|
663
|
+
const key = cells[0];
|
|
664
|
+
if (rows.has(key)) {
|
|
665
|
+
problems.repeated++;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
rows.set(key, cells.slice(0, columns.length));
|
|
669
|
+
lines.set(key, record.line);
|
|
670
|
+
}
|
|
671
|
+
if (problems.short + problems.long + problems.repeated > 0) {
|
|
672
|
+
report.warning(
|
|
673
|
+
"parse-error",
|
|
674
|
+
CYS_ISSUE.TABLE_ROW,
|
|
675
|
+
`${entry}: ${problems.short} row(s) with too few cells (the rest are unset), ${problems.long} with too many (the extra cells are ignored), ${problems.repeated} repeating a key (the first row is read)`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
return { path, entry, columns, rows, lines };
|
|
679
|
+
}
|
|
680
|
+
function columnOf(name, javaClass) {
|
|
681
|
+
const scalar = JAVA_TYPES[javaClass.trim()];
|
|
682
|
+
if (scalar !== void 0) {
|
|
683
|
+
return { name, type: scalar, list: false };
|
|
684
|
+
}
|
|
685
|
+
const list = LIST_CLASS.exec(javaClass.trim());
|
|
686
|
+
const item = list === null ? void 0 : JAVA_TYPES[list[1]];
|
|
687
|
+
return item === void 0 ? null : { name, type: item, list: true };
|
|
688
|
+
}
|
|
689
|
+
function tableError(report, entry, reason) {
|
|
690
|
+
report.error("parse-error", CYS_ISSUE.TABLE, `${entry}: ${reason}; the table is not read`);
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
function cellAtt(column, text, line, namespace, hidden) {
|
|
694
|
+
if (text.startsWith("=")) {
|
|
695
|
+
return att(column.name, column.list ? "List" : column.type, null, text, line, namespace, hidden, true);
|
|
696
|
+
}
|
|
697
|
+
if (!column.list) {
|
|
698
|
+
if (text.length === 0 && column.type !== "String") {
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
return att(column.name, column.type, null, text, line, namespace, hidden, false);
|
|
702
|
+
}
|
|
703
|
+
if (text.length === 0 && column.type !== "String") {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
const list = att(column.name, "List", column.type, null, line, namespace, hidden, false);
|
|
707
|
+
for (const item of javaSplitLines(text)) {
|
|
708
|
+
if (item.length === 0 && column.type !== "String") {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
list.children.push(att(column.name, column.type, null, item, line, namespace, hidden, false));
|
|
712
|
+
}
|
|
713
|
+
return list;
|
|
714
|
+
}
|
|
715
|
+
function javaSplitLines(text) {
|
|
716
|
+
if (!text.includes("\n")) {
|
|
717
|
+
return [text];
|
|
718
|
+
}
|
|
719
|
+
const items = text.split("\n");
|
|
720
|
+
while (items.length > 0 && items[items.length - 1].length === 0) {
|
|
721
|
+
items.pop();
|
|
722
|
+
}
|
|
723
|
+
return items;
|
|
724
|
+
}
|
|
725
|
+
function att(name, cyType, elementType, value, line, namespace, hidden, equation) {
|
|
726
|
+
return {
|
|
727
|
+
name,
|
|
728
|
+
type: null,
|
|
729
|
+
cyType,
|
|
730
|
+
elementType,
|
|
731
|
+
value,
|
|
732
|
+
hidden,
|
|
733
|
+
equation,
|
|
734
|
+
children: [],
|
|
735
|
+
extra: {},
|
|
736
|
+
xml: null,
|
|
737
|
+
hasGraph: false,
|
|
738
|
+
text: "",
|
|
739
|
+
line,
|
|
740
|
+
namespace
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
async function virtualColumnsOf(target, virtuals, tableOf, report) {
|
|
744
|
+
const out = [];
|
|
745
|
+
for (const virtual of virtuals) {
|
|
746
|
+
if (virtual.targetTable !== target.path) {
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
const resolved = await resolve(virtual, virtuals, tableOf, /* @__PURE__ */ new Set());
|
|
750
|
+
if (typeof resolved === "string") {
|
|
751
|
+
report.error(
|
|
752
|
+
"parse-error",
|
|
753
|
+
CYS_ISSUE.TABLE,
|
|
754
|
+
`${target.entry}: the virtual column "${virtual.name}" ${resolved}; the column is not read`
|
|
755
|
+
);
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const targetKey = target.columns.findIndex((c) => c.name === virtual.targetJoinKey);
|
|
759
|
+
const values = /* @__PURE__ */ new Map();
|
|
760
|
+
for (const [key, cells] of target.rows) {
|
|
761
|
+
const join = targetKey <= 0 ? key : cells[targetKey];
|
|
762
|
+
const value = join === void 0 ? void 0 : resolved.values.get(join);
|
|
763
|
+
if (value !== void 0) {
|
|
764
|
+
values.set(key, value);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
out.push({ column: { ...resolved.column, name: virtual.name }, values });
|
|
768
|
+
}
|
|
769
|
+
return out;
|
|
770
|
+
}
|
|
771
|
+
async function resolve(virtual, virtuals, tableOf, seen) {
|
|
772
|
+
if (seen.has(virtual)) {
|
|
773
|
+
return "is part of a cycle of virtual columns";
|
|
774
|
+
}
|
|
775
|
+
seen.add(virtual);
|
|
776
|
+
const source = await tableOf(virtual.sourceTable);
|
|
777
|
+
if (source === null) {
|
|
778
|
+
return `names the source table "${virtual.sourceTable}", which the session does not hold or cannot read`;
|
|
779
|
+
}
|
|
780
|
+
const joinIndex = source.columns.findIndex((c) => c.name === virtual.sourceJoinKey);
|
|
781
|
+
if (joinIndex < 0) {
|
|
782
|
+
return `joins on the column "${virtual.sourceJoinKey}", which its source table does not have`;
|
|
783
|
+
}
|
|
784
|
+
const joinOf = (key, cells) => joinIndex === 0 ? key : cells[joinIndex];
|
|
785
|
+
const own = source.columns.findIndex((c) => c.name === virtual.sourceColumn);
|
|
786
|
+
if (own >= 0) {
|
|
787
|
+
const values2 = /* @__PURE__ */ new Map();
|
|
788
|
+
for (const [key, cells] of source.rows) {
|
|
789
|
+
const join = joinOf(key, cells);
|
|
790
|
+
if (join !== void 0 && cells[own] !== void 0) {
|
|
791
|
+
values2.set(join, cells[own]);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return { column: source.columns[own], values: values2 };
|
|
795
|
+
}
|
|
796
|
+
const chained = virtuals.find((v) => v.targetTable === source.path && v.name === virtual.sourceColumn);
|
|
797
|
+
if (chained === void 0) {
|
|
798
|
+
return `names the source column "${virtual.sourceColumn}", which its source table does not have`;
|
|
799
|
+
}
|
|
800
|
+
const inner = await resolve(chained, virtuals, tableOf, seen);
|
|
801
|
+
if (typeof inner === "string") {
|
|
802
|
+
return inner;
|
|
803
|
+
}
|
|
804
|
+
const values = /* @__PURE__ */ new Map();
|
|
805
|
+
const chainedJoin = source.columns.findIndex((c) => c.name === chained.targetJoinKey);
|
|
806
|
+
for (const [key, cells] of source.rows) {
|
|
807
|
+
const lookup = chainedJoin <= 0 ? key : cells[chainedJoin];
|
|
808
|
+
const value = lookup === void 0 ? void 0 : inner.values.get(lookup);
|
|
809
|
+
const join = joinOf(key, cells);
|
|
810
|
+
if (value !== void 0 && join !== void 0) {
|
|
811
|
+
values.set(join, value);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return { column: inner.column, values };
|
|
815
|
+
}
|
|
816
|
+
const FORMAT_DEFAULTS = {
|
|
817
|
+
ids: "keep",
|
|
818
|
+
defaultDirected: true,
|
|
819
|
+
weightFrom: null,
|
|
820
|
+
addMissingNodes: false
|
|
821
|
+
};
|
|
822
|
+
const USED_OPTIONS = /* @__PURE__ */ new Set([
|
|
823
|
+
"ids",
|
|
824
|
+
"addMissingNodes",
|
|
825
|
+
"duplicateEdges",
|
|
826
|
+
"selfLoops",
|
|
827
|
+
"onMixedDirection",
|
|
828
|
+
"defaultDirected",
|
|
829
|
+
"weightFrom",
|
|
830
|
+
"weightDtype",
|
|
831
|
+
"long",
|
|
832
|
+
"errorLimit",
|
|
833
|
+
"signal",
|
|
834
|
+
"onProgress"
|
|
835
|
+
]);
|
|
836
|
+
function budgetOption(value) {
|
|
837
|
+
if (value === void 0) {
|
|
838
|
+
return DEFAULT_MAX_UNCOMPRESSED;
|
|
839
|
+
}
|
|
840
|
+
if (typeof value !== "number" || !(value > 0)) {
|
|
841
|
+
throw new GraphFormatError(
|
|
842
|
+
"E_UNSUPPORTED",
|
|
843
|
+
`option maxUncompressedBytes: ${JSON.stringify(value)} is not a positive number`,
|
|
844
|
+
{
|
|
845
|
+
option: "maxUncompressedBytes",
|
|
846
|
+
found: value
|
|
847
|
+
}
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
return value;
|
|
851
|
+
}
|
|
852
|
+
async function prepare(input, sink, options) {
|
|
853
|
+
const common = resolveImportOptions(options, FORMAT_DEFAULTS);
|
|
854
|
+
const report = new ImportReportBuilder(FORMAT, common.errorLimit);
|
|
855
|
+
if (sink !== null) {
|
|
856
|
+
reportSinkOptions(sink, options, report, true);
|
|
857
|
+
reportUnusedOptions(options, report, USED_OPTIONS);
|
|
858
|
+
}
|
|
859
|
+
const { zAs } = resolveSettings({ zAs: options?.zAs }, false);
|
|
860
|
+
const maxBytes = budgetOption(options?.maxUncompressedBytes);
|
|
861
|
+
const session = await openSession(input, report, common, maxBytes);
|
|
862
|
+
const inner = Object.freeze({ ...common, onProgress: null, encoding: null });
|
|
863
|
+
const docs = /* @__PURE__ */ new Map();
|
|
864
|
+
const choices = session.layout.era === "3" ? await networks3(session, report, inner, docs) : await networks2(session, report, inner);
|
|
865
|
+
return { report, common, inner, zAs, session, choices, docs };
|
|
866
|
+
}
|
|
867
|
+
async function parseEntry(session, entry, report, inner) {
|
|
868
|
+
const bytes = await session.read(entry);
|
|
869
|
+
const scratch = new ImportReportBuilder(FORMAT, Number.MAX_SAFE_INTEGER);
|
|
870
|
+
try {
|
|
871
|
+
const doc = await parseXgmml(bytes, scratch, inner, void 0);
|
|
872
|
+
const dialect = dialectOf(doc, scratch);
|
|
873
|
+
relay(scratch.issues, report, entry.name);
|
|
874
|
+
return { doc, dialect };
|
|
875
|
+
} catch (err) {
|
|
876
|
+
if (!(err instanceof ImportError)) {
|
|
877
|
+
throw err;
|
|
878
|
+
}
|
|
879
|
+
const { issues } = scratch;
|
|
880
|
+
const fatal = issues.at(-1);
|
|
881
|
+
relay(issues.slice(0, -1), report, entry.name);
|
|
882
|
+
return report.fail(fatal?.code ?? CYS_ISSUE.CORRUPT, `${entry.name}: ${fatal?.message ?? err.message}`, {
|
|
883
|
+
line: fatal?.line ?? null
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function relay(issues, report, entry) {
|
|
888
|
+
for (const issue of issues) {
|
|
889
|
+
const where = { line: issue.line, element: issue.element };
|
|
890
|
+
if (issue.severity === "error") {
|
|
891
|
+
report.error(issue.category, issue.code, `${entry}: ${issue.message}`, where);
|
|
892
|
+
} else {
|
|
893
|
+
report.warning(issue.category, issue.code, `${entry}: ${issue.message}`, where);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
async function networks3(session, report, inner, docs) {
|
|
898
|
+
const { layout } = session;
|
|
899
|
+
const choices = [];
|
|
900
|
+
for (const network of layout.networks) {
|
|
901
|
+
const parsed = await parseEntry(session, network, report, inner);
|
|
902
|
+
docs.set(network.name, parsed);
|
|
903
|
+
for (const graph of registeredGraphs(parsed)) {
|
|
904
|
+
const members = membersOf(parsed.doc, graph);
|
|
905
|
+
choices.push({
|
|
906
|
+
era: "3",
|
|
907
|
+
network,
|
|
908
|
+
graphId: graph.id ?? "",
|
|
909
|
+
name: graph.label ?? graph.id,
|
|
910
|
+
nodes: new Set(members.nodes).size,
|
|
911
|
+
edges: members.edges.length
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const known = new Set(choices.map((c) => c.graphId));
|
|
916
|
+
const strayViews = layout.views.filter((v) => !known.has(v.network)).map((v) => v.name);
|
|
917
|
+
const strayTables = layout.tables.filter(
|
|
918
|
+
(t) => !known.has(t.network) && !layout.networks.some((n) => n.suid === t.network) && !isGroupTable(t, session, docs)
|
|
919
|
+
);
|
|
920
|
+
if (strayViews.length > 0 || strayTables.length > 0) {
|
|
921
|
+
report.warning(
|
|
922
|
+
"validation-error",
|
|
923
|
+
CYS_ISSUE.DANGLING_REFERENCE,
|
|
924
|
+
`${strayViews.length + strayTables.length} view(s) or table(s) belong to a network the session does not hold and were not read: ${listed([...strayViews, ...strayTables.map((t) => t.name)])}`
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
if (layout.networkList === null) {
|
|
928
|
+
return choices;
|
|
929
|
+
}
|
|
930
|
+
const tree = await readXmlTree(await session.read(layout.networkList), layout.networkList.name, report, inner);
|
|
931
|
+
const order = /* @__PURE__ */ new Map();
|
|
932
|
+
for (const node of elementsNamed(tree, "network")) {
|
|
933
|
+
const id = node.attrs.get("id");
|
|
934
|
+
const position = Number(node.attrs.get("order"));
|
|
935
|
+
if (id !== void 0 && Number.isFinite(position)) {
|
|
936
|
+
order.set(id, position);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return choices.map((choice, index) => ({ choice, index })).sort(
|
|
940
|
+
(a, b) => (order.get(a.choice.graphId) ?? Infinity) - (order.get(b.choice.graphId) ?? Infinity) || a.index - b.index
|
|
941
|
+
).map((c) => c.choice);
|
|
942
|
+
}
|
|
943
|
+
function isGroupTable(table, session, docs) {
|
|
944
|
+
for (const network of session.layout.networks) {
|
|
945
|
+
if (docs.get(network.name)?.doc.graphs.some((g) => g.id === table.network) === true) {
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
function registeredGraphs(parsed) {
|
|
952
|
+
return parsed.doc.root.subgraphs.filter((g) => isCyTrue(g.registered));
|
|
953
|
+
}
|
|
954
|
+
async function import3(prepared, choice, sink, parsed) {
|
|
955
|
+
const { report, inner, session } = prepared;
|
|
956
|
+
const { layout } = session;
|
|
957
|
+
const { doc, dialect } = parsed;
|
|
958
|
+
const graph = doc.root.subgraphs.find((g) => g.id === choice.graphId);
|
|
959
|
+
const members = membersOf(doc, graph);
|
|
960
|
+
const nodes = byId(members.nodes);
|
|
961
|
+
const edges = byId(members.edges);
|
|
962
|
+
const cache = /* @__PURE__ */ new Map();
|
|
963
|
+
const tableOf = (path) => {
|
|
964
|
+
let pending = cache.get(path);
|
|
965
|
+
if (pending === void 0) {
|
|
966
|
+
const entry = layout.tables.find((t) => t.tablePath === path);
|
|
967
|
+
pending = entry === void 0 ? Promise.resolve(null) : session.read(entry).then((bytes) => readCyTable(bytes, path, entry.name, report, inner));
|
|
968
|
+
cache.set(path, pending);
|
|
969
|
+
}
|
|
970
|
+
return pending;
|
|
971
|
+
};
|
|
972
|
+
const virtuals = layout.cytables === null ? [] : await readVirtuals(session, layout.cytables, report, inner);
|
|
973
|
+
const declared = /* @__PURE__ */ new Set([
|
|
974
|
+
...doc.nodes.flatMap((n) => n.id === null ? [] : [n.id]),
|
|
975
|
+
...doc.edges.flatMap((e) => e.id === null ? [] : [e.id])
|
|
976
|
+
]);
|
|
977
|
+
let unmatched = 0;
|
|
978
|
+
for (const entry of layout.tables.filter((t) => t.network === choice.graphId && t.element !== null)) {
|
|
979
|
+
throwIfAborted(inner.signal);
|
|
980
|
+
const table = await tableOf(entry.tablePath);
|
|
981
|
+
if (table === null) {
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
const local = entry.namespace === "LOCAL_ATTRS";
|
|
985
|
+
const shared = entry.namespace === "SHARED_ATTRS";
|
|
986
|
+
const namespace = local ? null : entry.namespace;
|
|
987
|
+
const hidden = !local && !shared;
|
|
988
|
+
const target = (key) => {
|
|
989
|
+
if (entry.element === "network") {
|
|
990
|
+
return key === choice.graphId ? graph.atts : void 0;
|
|
991
|
+
}
|
|
992
|
+
return (entry.element === "node" ? nodes : edges).get(key)?.atts;
|
|
993
|
+
};
|
|
994
|
+
for (const [key, cells] of table.rows) {
|
|
995
|
+
const atts = target(key);
|
|
996
|
+
if (atts === void 0) {
|
|
997
|
+
if (!declared.has(key)) {
|
|
998
|
+
unmatched++;
|
|
999
|
+
}
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
const line = table.lines.get(key) ?? 0;
|
|
1003
|
+
for (let i = 1; i < table.columns.length && i < cells.length; i++) {
|
|
1004
|
+
const att2 = cellAtt(table.columns[i], cells[i], line, namespace, hidden);
|
|
1005
|
+
if (att2 !== null) {
|
|
1006
|
+
atts.push(att2);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
for (const virtual of await virtualColumnsOf(table, virtuals, tableOf, report)) {
|
|
1011
|
+
const owned = table.columns.some((c) => c.name === virtual.column.name);
|
|
1012
|
+
let ns = namespace;
|
|
1013
|
+
if (local) {
|
|
1014
|
+
ns = owned ? "SHARED_ATTRS" : null;
|
|
1015
|
+
}
|
|
1016
|
+
for (const [key, text] of virtual.values) {
|
|
1017
|
+
const att2 = cellAtt(virtual.column, text, table.lines.get(key) ?? 0, ns, hidden);
|
|
1018
|
+
if (att2 !== null) {
|
|
1019
|
+
target(key)?.push(att2);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (unmatched > 0) {
|
|
1025
|
+
report.warning(
|
|
1026
|
+
"parse-error",
|
|
1027
|
+
CYS_ISSUE.TABLE_ROW,
|
|
1028
|
+
`${unmatched} table row(s) of network ${choice.graphId} name no node, edge or network of it (stale rows); they are not read`
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
const views = layout.views.filter((v) => v.network === choice.graphId);
|
|
1032
|
+
let visualStyle = null;
|
|
1033
|
+
const extraViews = [];
|
|
1034
|
+
for (const [i, view] of views.entries()) {
|
|
1035
|
+
const viewDoc = (await parseEntry(session, view, report, inner)).doc;
|
|
1036
|
+
if (i === 0) {
|
|
1037
|
+
visualStyle = viewDoc.root.attrs.get("cy:visualStyle") ?? null;
|
|
1038
|
+
applyView(viewDoc, graph, nodes, edges, report, view.name);
|
|
1039
|
+
} else {
|
|
1040
|
+
extraViews.push(viewPositions(viewDoc, prepared.zAs));
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
const cyMeta = sessionMeta(prepared, {
|
|
1044
|
+
collection: doc.root.label ?? choice.network.title,
|
|
1045
|
+
network: choice.graphId,
|
|
1046
|
+
parentNetwork: parentNetwork(graph, prepared.choices),
|
|
1047
|
+
visualStyle
|
|
1048
|
+
});
|
|
1049
|
+
const groups = [];
|
|
1050
|
+
cyMeta.groups = groups;
|
|
1051
|
+
const extras = {
|
|
1052
|
+
sourceFormat: FORMAT,
|
|
1053
|
+
entry: choice.network.name,
|
|
1054
|
+
graphName: localName(graph) ?? choice.name,
|
|
1055
|
+
metaExtra: { [META_KEY]: cyMeta },
|
|
1056
|
+
groupNodes: new Set(
|
|
1057
|
+
members.nodes.filter((n) => n.atts.some((a) => a.name === "__isGroup" && isCyTrue(a.value))).flatMap((n) => n.id === null ? [] : [n.id])
|
|
1058
|
+
),
|
|
1059
|
+
resolvePointer: pointerResolver(prepared, doc),
|
|
1060
|
+
onMissingMember: (group, member) => {
|
|
1061
|
+
const record = groups.find((g) => g.group === group);
|
|
1062
|
+
if (record === void 0) {
|
|
1063
|
+
groups.push({ group, members: [member] });
|
|
1064
|
+
} else if (!record.members.includes(member)) {
|
|
1065
|
+
record.members.push(member);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
const settings = { labelAliases: false, cytoscapeEscapes: false, zAs: prepared.zAs };
|
|
1070
|
+
new XgmmlEmitter(doc, dialect, sink, report, prepared.inner, settings, extras).emit(graph);
|
|
1071
|
+
if (groups.length > 0) {
|
|
1072
|
+
report.warning(
|
|
1073
|
+
"unsupported",
|
|
1074
|
+
CYS_ISSUE.COLLAPSED_GROUP,
|
|
1075
|
+
`${groups.length} collapsed group(s) hold ${groups.reduce((n, g) => n + g.members.length, 0)} member(s) that are not in the network; they are listed in meta.extra.cytoscape.groups`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
reportRootOnly(parsed, report, choice.network.name);
|
|
1079
|
+
writeViewPositions(
|
|
1080
|
+
sink,
|
|
1081
|
+
extraViews,
|
|
1082
|
+
views.slice(1).map((v) => v.view),
|
|
1083
|
+
report,
|
|
1084
|
+
prepared.inner
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
async function readVirtuals(session, entry, report, inner) {
|
|
1088
|
+
const tree = await readXmlTree(await session.read(entry), entry.name, report, inner);
|
|
1089
|
+
const out = [];
|
|
1090
|
+
for (const node of elementsNamed(tree, "virtualColumn")) {
|
|
1091
|
+
const a = (key) => node.attrs.get(key) ?? "";
|
|
1092
|
+
out.push({
|
|
1093
|
+
name: a("name"),
|
|
1094
|
+
targetTable: a("targetTable"),
|
|
1095
|
+
sourceTable: a("sourceTable"),
|
|
1096
|
+
sourceColumn: a("sourceColumn"),
|
|
1097
|
+
sourceJoinKey: node.attrs.get("sourceJoinKey") ?? "SUID",
|
|
1098
|
+
targetJoinKey: node.attrs.get("targetJoinKey") ?? "SUID"
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
return out;
|
|
1102
|
+
}
|
|
1103
|
+
function byId(records) {
|
|
1104
|
+
const out = /* @__PURE__ */ new Map();
|
|
1105
|
+
for (const record of records) {
|
|
1106
|
+
if (record.id !== null && !out.has(record.id)) {
|
|
1107
|
+
out.set(record.id, record);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return out;
|
|
1111
|
+
}
|
|
1112
|
+
function applyView(view, graph, nodes, edges, report, entry) {
|
|
1113
|
+
let dangling = 0;
|
|
1114
|
+
for (const node of view.nodes) {
|
|
1115
|
+
const target = node.viewId === null ? void 0 : nodes.get(node.viewId);
|
|
1116
|
+
if (target === void 0) {
|
|
1117
|
+
dangling++;
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
target.x = node.x;
|
|
1121
|
+
target.y = node.y;
|
|
1122
|
+
target.z = node.z;
|
|
1123
|
+
if (node.graphics !== null) {
|
|
1124
|
+
target.graphics = { ...target.graphics ?? {}, ...node.graphics };
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
for (const edge of view.edges) {
|
|
1128
|
+
const target = edge.viewId === null ? void 0 : edges.get(edge.viewId);
|
|
1129
|
+
if (target === void 0) {
|
|
1130
|
+
dangling++;
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1133
|
+
if (edge.graphics !== null) {
|
|
1134
|
+
target.graphics = { ...target.graphics ?? {}, ...edge.graphics };
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
if (view.root.graphics !== null) {
|
|
1138
|
+
graph.graphics = { ...graph.graphics ?? {}, ...view.root.graphics };
|
|
1139
|
+
}
|
|
1140
|
+
if (dangling > 0) {
|
|
1141
|
+
report.warning(
|
|
1142
|
+
"validation-error",
|
|
1143
|
+
CYS_ISSUE.DANGLING_REFERENCE,
|
|
1144
|
+
`${entry}: ${dangling} view element(s) name no node or edge of the network; they are not read`
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
function viewPositions(view, zAs) {
|
|
1149
|
+
const out = /* @__PURE__ */ new Map();
|
|
1150
|
+
for (const node of view.nodes) {
|
|
1151
|
+
const x = Number(node.x);
|
|
1152
|
+
const y = Number(node.y);
|
|
1153
|
+
const z = zAs === "position" ? Number(node.z ?? 0) : 0;
|
|
1154
|
+
if (node.viewId !== null && node.x !== null && node.y !== null && Number.isFinite(x) && Number.isFinite(y)) {
|
|
1155
|
+
out.set(node.viewId, [x, y === 0 ? 0 : -y, Number.isFinite(z) ? z : 0]);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return out;
|
|
1159
|
+
}
|
|
1160
|
+
function writeViewPositions(sink, views, viewIds, report, common) {
|
|
1161
|
+
const coercer = new IdCoercer(common.ids);
|
|
1162
|
+
views.forEach((positions, i) => {
|
|
1163
|
+
const name = `${VIEW_POSITION_PREFIX}${i + 2}`;
|
|
1164
|
+
const { handle } = declareResolved(
|
|
1165
|
+
sink,
|
|
1166
|
+
"node",
|
|
1167
|
+
{
|
|
1168
|
+
name,
|
|
1169
|
+
dtype: "f32",
|
|
1170
|
+
components: 3,
|
|
1171
|
+
nullable: true,
|
|
1172
|
+
origin: {
|
|
1173
|
+
format: FORMAT,
|
|
1174
|
+
id: viewIds[i],
|
|
1175
|
+
title: null,
|
|
1176
|
+
type: "graphics",
|
|
1177
|
+
namespace: XGMML_ORIGIN_NAMESPACE
|
|
1178
|
+
},
|
|
1179
|
+
extra: { sourceDims: 2, units: "file" }
|
|
1180
|
+
},
|
|
1181
|
+
report,
|
|
1182
|
+
{ element: name }
|
|
1183
|
+
);
|
|
1184
|
+
for (const [id, xyz] of positions) {
|
|
1185
|
+
let index;
|
|
1186
|
+
try {
|
|
1187
|
+
index = sink.indexOf(coercer.text(id));
|
|
1188
|
+
} catch {
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
if (index !== INVALID_INDEX) {
|
|
1192
|
+
sink.setNodeValue(handle, index, xyz);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
function localName(graph) {
|
|
1198
|
+
const att2 = graph.atts.find((a) => a.name === "name" && (a.namespace ?? null) === null);
|
|
1199
|
+
return att2?.value ?? null;
|
|
1200
|
+
}
|
|
1201
|
+
function parentNetwork(graph, choices) {
|
|
1202
|
+
const att2 = graph.atts.find((a) => a.name === "__parentNetwork.SUID" || a.name === "Cy2 Parent Network.SUID");
|
|
1203
|
+
if (att2?.value === void 0 || att2.value === null || att2.value.length === 0) {
|
|
1204
|
+
return null;
|
|
1205
|
+
}
|
|
1206
|
+
const suid = att2.value;
|
|
1207
|
+
const named = choices.find((c) => c.era === "3" && c.graphId === suid);
|
|
1208
|
+
return named?.name ?? suid;
|
|
1209
|
+
}
|
|
1210
|
+
function pointerResolver(prepared, doc) {
|
|
1211
|
+
const names = /* @__PURE__ */ new Map();
|
|
1212
|
+
for (const graph of doc.graphs) {
|
|
1213
|
+
const { href } = graph;
|
|
1214
|
+
const hash = href?.indexOf("#") ?? -1;
|
|
1215
|
+
if (href === null || hash <= 0 || names.has(href)) {
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1218
|
+
const file = urlDecode(href.slice(0, hash));
|
|
1219
|
+
const id = href.slice(hash + 1);
|
|
1220
|
+
const network = prepared.session.layout.networks.find(
|
|
1221
|
+
(n) => urlDecode(n.path.slice("networks/".length)) === file
|
|
1222
|
+
);
|
|
1223
|
+
const parsed = network === void 0 ? void 0 : prepared.docs.get(network.name);
|
|
1224
|
+
const target = parsed?.doc.graphs.find((g) => g.id === id);
|
|
1225
|
+
if (target !== void 0) {
|
|
1226
|
+
names.set(href, target.label ?? id);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return (href) => names.get(href) ?? null;
|
|
1230
|
+
}
|
|
1231
|
+
function reportRootOnly(parsed, report, entry) {
|
|
1232
|
+
const held = /* @__PURE__ */ new Set();
|
|
1233
|
+
for (const graph of registeredGraphs(parsed)) {
|
|
1234
|
+
const members = membersOf(parsed.doc, graph);
|
|
1235
|
+
members.nodes.forEach((n) => held.add(n));
|
|
1236
|
+
members.edges.forEach((e) => held.add(e));
|
|
1237
|
+
}
|
|
1238
|
+
const nodes = parsed.doc.nodes.filter((n) => !held.has(n)).length;
|
|
1239
|
+
const edges = parsed.doc.edges.filter((e) => !held.has(e)).length;
|
|
1240
|
+
if (nodes + edges > 0) {
|
|
1241
|
+
report.warning(
|
|
1242
|
+
"unsupported",
|
|
1243
|
+
XGMML_ISSUE.ROOT_ONLY_ELEMENTS,
|
|
1244
|
+
`${entry}: ${nodes} node(s) and ${edges} edge(s) belong to no registered network (group meta-edges, collapsed group members) and were not read`
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
async function networks2(session, report, inner) {
|
|
1249
|
+
const { layout } = session;
|
|
1250
|
+
if (layout.cysession === null) {
|
|
1251
|
+
return report.fail(CYS_ISSUE.NOT_SESSION, "the session has no cysession.xml");
|
|
1252
|
+
}
|
|
1253
|
+
const tree = await readXmlTree(await session.read(layout.cysession), layout.cysession.name, report, inner);
|
|
1254
|
+
const documentVersion = tree.attrs.get("documentVersion") ?? "";
|
|
1255
|
+
if (documentVersion.startsWith("3")) {
|
|
1256
|
+
report.fail(
|
|
1257
|
+
CYS_ISSUE.VERSION,
|
|
1258
|
+
`cysession.xml has documentVersion ${documentVersion} and no version marker: the 2011 Cytoscape 3.0 pre-release layout, which no released Cytoscape reads`
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
const byDecoded = /* @__PURE__ */ new Map();
|
|
1262
|
+
for (const [path, entry] of layout.files) {
|
|
1263
|
+
byDecoded.set(path, entry);
|
|
1264
|
+
byDecoded.set(urlDecode(path), entry);
|
|
1265
|
+
}
|
|
1266
|
+
const order = [...layout.files.values()];
|
|
1267
|
+
const choices = [];
|
|
1268
|
+
const missing = [];
|
|
1269
|
+
const named = /* @__PURE__ */ new Set();
|
|
1270
|
+
for (const record of elementsNamed(tree, "network")) {
|
|
1271
|
+
const id = record.attrs.get("id") ?? "";
|
|
1272
|
+
const filename = record.attrs.get("filename") ?? `${id}.xgmml`;
|
|
1273
|
+
if (id === "Network Root") {
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
const file = byDecoded.get(filename);
|
|
1277
|
+
if (file === void 0) {
|
|
1278
|
+
missing.push(filename);
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
named.add(file);
|
|
1282
|
+
const parent = record.children.find((c) => c.name === "parent")?.attrs.get("id") ?? null;
|
|
1283
|
+
choices.push({
|
|
1284
|
+
era: "2",
|
|
1285
|
+
file,
|
|
1286
|
+
name: id,
|
|
1287
|
+
record,
|
|
1288
|
+
parent: parent === "Network Root" || parent === "NULL" ? null : parent
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
if (missing.length > 0) {
|
|
1292
|
+
report.warning(
|
|
1293
|
+
"validation-error",
|
|
1294
|
+
CYS_ISSUE.DANGLING_REFERENCE,
|
|
1295
|
+
`cysession.xml names ${missing.length} network file(s) the session does not hold: ${listed(missing)}`
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
const unnamed = order.filter((e) => !named.has(e)).map((e) => e.name);
|
|
1299
|
+
if (unnamed.length > 0) {
|
|
1300
|
+
report.warning(
|
|
1301
|
+
"unsupported",
|
|
1302
|
+
CYS_ISSUE.ENTRY_SKIPPED,
|
|
1303
|
+
`${unnamed.length} network file(s) are not in cysession.xml's network tree and were not read: ${listed(unnamed)}`
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
return choices.sort((a, b) => order.indexOf(a.file) - order.indexOf(b.file));
|
|
1307
|
+
}
|
|
1308
|
+
async function import2(prepared, choice, sink) {
|
|
1309
|
+
const { report, inner, session } = prepared;
|
|
1310
|
+
const { doc, dialect } = await parseEntry(session, choice.file, report, inner);
|
|
1311
|
+
for (const [list, column] of [
|
|
1312
|
+
["selectedNodes", SELECTED_COLUMN],
|
|
1313
|
+
["hiddenNodes", HIDDEN_COLUMN]
|
|
1314
|
+
]) {
|
|
1315
|
+
mark(doc.nodes, idsIn(choice.record, list), column);
|
|
1316
|
+
}
|
|
1317
|
+
for (const [list, column] of [
|
|
1318
|
+
["selectedEdges", SELECTED_COLUMN],
|
|
1319
|
+
["hiddenEdges", HIDDEN_COLUMN]
|
|
1320
|
+
]) {
|
|
1321
|
+
mark(doc.edges, idsIn(choice.record, list), column);
|
|
1322
|
+
}
|
|
1323
|
+
const cyMeta = sessionMeta(prepared, {
|
|
1324
|
+
collection: null,
|
|
1325
|
+
network: choice.name,
|
|
1326
|
+
parentNetwork: choice.parent,
|
|
1327
|
+
visualStyle: choice.record.attrs.get("visualStyle") ?? null
|
|
1328
|
+
});
|
|
1329
|
+
const extras = {
|
|
1330
|
+
sourceFormat: FORMAT,
|
|
1331
|
+
entry: choice.file.name,
|
|
1332
|
+
graphName: choice.name,
|
|
1333
|
+
metaExtra: { [META_KEY]: cyMeta }
|
|
1334
|
+
};
|
|
1335
|
+
const settings = { labelAliases: true, cytoscapeEscapes: true, zAs: prepared.zAs };
|
|
1336
|
+
new XgmmlEmitter(doc, dialect, sink, report, inner, settings, extras).emit(null);
|
|
1337
|
+
}
|
|
1338
|
+
function idsIn(record, list) {
|
|
1339
|
+
const out = /* @__PURE__ */ new Set();
|
|
1340
|
+
for (const holder of record.children.filter((c) => c.name === list)) {
|
|
1341
|
+
for (const item of holder.children) {
|
|
1342
|
+
const id = item.attrs.get("id");
|
|
1343
|
+
if (id !== void 0) {
|
|
1344
|
+
out.add(id);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
return out;
|
|
1349
|
+
}
|
|
1350
|
+
function mark(records, ids, column) {
|
|
1351
|
+
if (ids.size === 0) {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
for (const record of records) {
|
|
1355
|
+
if (record.id !== null && ids.has(record.id) || record.label !== null && ids.has(record.label)) {
|
|
1356
|
+
record.atts.push({
|
|
1357
|
+
name: column,
|
|
1358
|
+
type: null,
|
|
1359
|
+
cyType: "Boolean",
|
|
1360
|
+
elementType: null,
|
|
1361
|
+
value: "true",
|
|
1362
|
+
hidden: false,
|
|
1363
|
+
equation: false,
|
|
1364
|
+
children: [],
|
|
1365
|
+
extra: {},
|
|
1366
|
+
xml: null,
|
|
1367
|
+
hasGraph: false,
|
|
1368
|
+
text: "",
|
|
1369
|
+
line: record.line,
|
|
1370
|
+
namespace: CYTOSCAPE_NAMESPACE
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
function sessionMeta(prepared, facts) {
|
|
1376
|
+
const { layout } = prepared.session;
|
|
1377
|
+
const { report } = prepared;
|
|
1378
|
+
if (layout.styles.length > 0) {
|
|
1379
|
+
report.warning(
|
|
1380
|
+
"unsupported",
|
|
1381
|
+
CYS_ISSUE.STYLES_NOT_IMPORTED,
|
|
1382
|
+
`the session's styles (${layout.styles.map((s) => s.path).join(", ")}) are not applied: style import is issue #706`
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
if (layout.skipped.length > 0) {
|
|
1386
|
+
report.warning(
|
|
1387
|
+
"unsupported",
|
|
1388
|
+
CYS_ISSUE.ENTRY_SKIPPED,
|
|
1389
|
+
`${layout.skipped.length} entries hold no graph data and were not read (apps, global tables, properties, images): ${listed(layout.skipped)}`
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
return {
|
|
1393
|
+
sessionVersion: layout.version,
|
|
1394
|
+
...facts,
|
|
1395
|
+
skippedEntries: [...layout.skipped]
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
async function importChoice(prepared, index, sink) {
|
|
1399
|
+
const choice = prepared.choices[index];
|
|
1400
|
+
if (choice.era === "3") {
|
|
1401
|
+
const parsed = prepared.docs.get(choice.network.name);
|
|
1402
|
+
await import3(prepared, choice, sink, parsed);
|
|
1403
|
+
} else {
|
|
1404
|
+
await import2(prepared, choice, sink);
|
|
1405
|
+
}
|
|
1406
|
+
throwIfAborted(prepared.common.signal);
|
|
1407
|
+
return prepared.report.finish();
|
|
1408
|
+
}
|
|
1409
|
+
async function importCys(input, sink, options) {
|
|
1410
|
+
const prepared = await prepare(input, sink, options);
|
|
1411
|
+
const index = chooseGraph(
|
|
1412
|
+
prepared.choices.map((c) => c.name),
|
|
1413
|
+
options,
|
|
1414
|
+
prepared.report
|
|
1415
|
+
);
|
|
1416
|
+
if (prepared.choices.length > 1) {
|
|
1417
|
+
prepared.report.warning(
|
|
1418
|
+
"unsupported",
|
|
1419
|
+
CYS_ISSUE.MULTIPLE_GRAPHS,
|
|
1420
|
+
`the session holds ${prepared.choices.length} networks; ${prepared.choices.length - 1} were not read (use importAll, graphIndex or graphName)`
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
return importChoice(prepared, index, sink);
|
|
1424
|
+
}
|
|
1425
|
+
async function importAllCys(input, sinkFor, options) {
|
|
1426
|
+
const bytes = await readBytes(input, { signal: options?.signal ?? null }) ?? input;
|
|
1427
|
+
const first = await prepare(bytes, null, options);
|
|
1428
|
+
const reports = [];
|
|
1429
|
+
for (let i = 0; i < first.choices.length; i++) {
|
|
1430
|
+
const sink = sinkFor(i);
|
|
1431
|
+
const prepared = i === 0 ? first : await prepare(bytes, null, options);
|
|
1432
|
+
reportSinkOptions(sink, options, prepared.report, true);
|
|
1433
|
+
reportUnusedOptions(options, prepared.report, USED_OPTIONS);
|
|
1434
|
+
reports.push(await importChoice(prepared, i, sink));
|
|
1435
|
+
}
|
|
1436
|
+
return reports;
|
|
1437
|
+
}
|
|
1438
|
+
async function listCysGraphs(input, options) {
|
|
1439
|
+
const prepared = await prepare(input, null, options);
|
|
1440
|
+
return prepared.choices.map(
|
|
1441
|
+
(choice, index) => Object.freeze({
|
|
1442
|
+
index,
|
|
1443
|
+
name: choice.name,
|
|
1444
|
+
nodes: choice.era === "3" ? choice.nodes : null,
|
|
1445
|
+
edges: choice.era === "3" ? choice.edges : null
|
|
1446
|
+
})
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
const SESSION_NAME = /CytoscapeSession|cysession\.xml|\d+\.\d+\.\d+\.version/;
|
|
1450
|
+
function sniffCys(head) {
|
|
1451
|
+
for (let at = 0; at + 4 <= head.byteLength; at++) {
|
|
1452
|
+
if (startsLikeZip(head.subarray(at))) {
|
|
1453
|
+
return SESSION_NAME.test(decodeEntryName(head.subarray(at))) ? 0.95 : 0;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
return 0;
|
|
1457
|
+
}
|
|
1458
|
+
const cysImporter = Object.freeze({
|
|
1459
|
+
format: FORMAT,
|
|
1460
|
+
extensions: EXTENSIONS,
|
|
1461
|
+
mimeTypes: MIME_TYPES,
|
|
1462
|
+
sniff: sniffCys,
|
|
1463
|
+
import: importCys,
|
|
1464
|
+
importAll: importAllCys,
|
|
1465
|
+
listGraphs: listCysGraphs
|
|
1466
|
+
});
|
|
1467
|
+
export {
|
|
1468
|
+
CYS_ISSUE as C,
|
|
1469
|
+
cysImporter as c
|
|
1470
|
+
};
|
|
1471
|
+
//# sourceMappingURL=importer-DD-xv9_Y.js.map
|