@graphty/graph-io 0.3.25 → 0.3.26
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 +49 -414
- package/dist/chunks/children-CL3Cy0ez.js.map +1 -1
- package/dist/chunks/choose-BbJI55dx.js +56 -0
- package/dist/chunks/choose-BbJI55dx.js.map +1 -0
- package/dist/chunks/constants-BCsFsMj4.js +231 -0
- package/dist/chunks/constants-BCsFsMj4.js.map +1 -0
- package/dist/chunks/{escape-B-tkk_Cl.js → escape-D0g6NNUy.js} +382 -92
- package/dist/chunks/escape-D0g6NNUy.js.map +1 -0
- package/dist/chunks/export-BTI4yQgy.js +4333 -0
- package/dist/chunks/export-BTI4yQgy.js.map +1 -0
- package/dist/chunks/exporter-BMqk5oxW.js +3465 -0
- package/dist/chunks/exporter-BMqk5oxW.js.map +1 -0
- package/dist/chunks/{importer-eUREhBGj.js → exporter-Cra2GYcN.js} +4969 -3201
- package/dist/chunks/exporter-Cra2GYcN.js.map +1 -0
- package/dist/chunks/{exporter-BGrsWamJ.js → exporter-Dxh231ZF.js} +38 -229
- package/dist/chunks/exporter-Dxh231ZF.js.map +1 -0
- package/dist/chunks/{exporter-DIeGJXAZ.js → exporter-tne475KK.js} +22 -20
- package/dist/chunks/exporter-tne475KK.js.map +1 -0
- package/dist/chunks/importer-90YWhJoF.js +2811 -0
- package/dist/chunks/importer-90YWhJoF.js.map +1 -0
- package/dist/chunks/{importer-D_e7e7LX.js → importer-Bcl1d4xH.js} +398 -145
- package/dist/chunks/importer-Bcl1d4xH.js.map +1 -0
- package/dist/chunks/{importer-BWY2FFc5.js → importer-BfIsp1f9.js} +255 -77
- package/dist/chunks/importer-BfIsp1f9.js.map +1 -0
- package/dist/chunks/{importer-zGLo8Dg_.js → importer-C_TEA1zJ.js} +484 -157
- package/dist/chunks/importer-C_TEA1zJ.js.map +1 -0
- package/dist/chunks/{importer-DET1tg6s.js → importer-DCjOvO7U.js} +1687 -310
- package/dist/chunks/importer-DCjOvO7U.js.map +1 -0
- package/dist/chunks/{importer-vi3bSdtw.js → importer-DfVu6j59.js} +509 -76
- package/dist/chunks/importer-DfVu6j59.js.map +1 -0
- package/dist/chunks/{importer-DIrbbnAf.js → importer-Dku_VAIY.js} +585 -226
- package/dist/chunks/importer-Dku_VAIY.js.map +1 -0
- package/dist/chunks/json-elements-3tnyt5uZ.js +1225 -0
- package/dist/chunks/json-elements-3tnyt5uZ.js.map +1 -0
- package/dist/chunks/ontology-export-B9Tcf37D.js +357 -0
- package/dist/chunks/ontology-export-B9Tcf37D.js.map +1 -0
- package/dist/chunks/{records-dbRkxwaq.js → records-CQXT__mY.js} +298 -114
- package/dist/chunks/records-CQXT__mY.js.map +1 -0
- package/dist/chunks/{text-Dr0Ifpag.js → text-DmcUs5_Q.js} +29 -11
- package/dist/chunks/text-DmcUs5_Q.js.map +1 -0
- package/dist/chunks/{writer-C7flM-Ih.js → writer-DxjgHwSe.js} +19 -1
- package/dist/chunks/writer-DxjgHwSe.js.map +1 -0
- package/dist/csv.js +951 -261
- package/dist/csv.js.map +1 -1
- package/dist/cx.js +6 -3
- package/dist/cx2.js +2 -2
- package/dist/cys.js +6 -3
- package/dist/dot.js +1 -1
- package/dist/gexf.js +554 -122
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +342 -85
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +1105 -213
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +3 -2
- package/dist/graphml.js.map +1 -1
- package/dist/json.js +3 -2
- package/dist/neo4j.js +564 -157
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +6 -3
- package/dist/pajek.js +1 -1
- package/dist/src/children.d.ts +18 -6
- package/dist/src/children.d.ts.map +1 -1
- package/dist/src/children.js +6 -3
- package/dist/src/children.js.map +1 -1
- package/dist/src/common/attributes.d.ts +29 -11
- package/dist/src/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +25 -7
- package/dist/src/common/attributes.js.map +1 -1
- package/dist/src/common/cell-budget.d.ts +9 -2
- package/dist/src/common/cell-budget.d.ts.map +1 -1
- package/dist/src/common/cell-budget.js +11 -3
- package/dist/src/common/cell-budget.js.map +1 -1
- package/dist/src/common/choose.d.ts +19 -0
- package/dist/src/common/choose.d.ts.map +1 -0
- package/dist/src/common/choose.js +72 -0
- package/dist/src/common/choose.js.map +1 -0
- package/dist/src/common/codes.d.ts +456 -66
- package/dist/src/common/codes.d.ts.map +1 -1
- package/dist/src/common/codes.js +458 -66
- package/dist/src/common/codes.js.map +1 -1
- package/dist/src/common/compare.d.ts +81 -0
- package/dist/src/common/compare.d.ts.map +1 -0
- package/dist/src/common/compare.js +394 -0
- package/dist/src/common/compare.js.map +1 -0
- package/dist/src/common/cx-export.d.ts +123 -0
- package/dist/src/common/cx-export.d.ts.map +1 -0
- package/dist/src/common/cx-export.js +334 -0
- package/dist/src/common/cx-export.js.map +1 -0
- package/dist/src/common/declared-types.d.ts +22 -2
- package/dist/src/common/declared-types.d.ts.map +1 -1
- package/dist/src/common/declared-types.js +19 -1
- package/dist/src/common/declared-types.js.map +1 -1
- package/dist/src/common/direction.d.ts +74 -50
- package/dist/src/common/direction.d.ts.map +1 -1
- package/dist/src/common/direction.js +47 -27
- package/dist/src/common/direction.js.map +1 -1
- package/dist/src/common/escape.d.ts +21 -9
- package/dist/src/common/escape.d.ts.map +1 -1
- package/dist/src/common/escape.js +33 -17
- package/dist/src/common/escape.js.map +1 -1
- package/dist/src/common/export.d.ts +144 -32
- package/dist/src/common/export.d.ts.map +1 -1
- package/dist/src/common/export.js +216 -42
- package/dist/src/common/export.js.map +1 -1
- package/dist/src/common/format.d.ts +23 -1
- package/dist/src/common/format.d.ts.map +1 -1
- package/dist/src/common/format.js +26 -1
- package/dist/src/common/format.js.map +1 -1
- package/dist/src/common/ids.d.ts +9 -2
- package/dist/src/common/ids.d.ts.map +1 -1
- package/dist/src/common/ids.js +6 -2
- package/dist/src/common/ids.js.map +1 -1
- package/dist/src/common/input.d.ts +122 -28
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +890 -124
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/json-elements.d.ts +144 -20
- package/dist/src/common/json-elements.d.ts.map +1 -1
- package/dist/src/common/json-elements.js +868 -188
- package/dist/src/common/json-elements.js.map +1 -1
- package/dist/src/common/line-format.d.ts +90 -0
- package/dist/src/common/line-format.d.ts.map +1 -0
- package/dist/src/common/line-format.js +185 -0
- package/dist/src/common/line-format.js.map +1 -0
- package/dist/src/common/lists.d.ts +6 -1
- package/dist/src/common/lists.d.ts.map +1 -1
- package/dist/src/common/lists.js +2 -0
- package/dist/src/common/lists.js.map +1 -1
- package/dist/src/common/ontology-export.d.ts +167 -0
- package/dist/src/common/ontology-export.d.ts.map +1 -0
- package/dist/src/common/ontology-export.js +359 -0
- package/dist/src/common/ontology-export.js.map +1 -0
- package/dist/src/common/ontology.d.ts +25 -3
- package/dist/src/common/ontology.d.ts.map +1 -1
- package/dist/src/common/ontology.js +30 -9
- package/dist/src/common/ontology.js.map +1 -1
- package/dist/src/common/options.d.ts +62 -12
- package/dist/src/common/options.d.ts.map +1 -1
- package/dist/src/common/options.js +89 -11
- package/dist/src/common/options.js.map +1 -1
- package/dist/src/common/plural.d.ts +31 -0
- package/dist/src/common/plural.d.ts.map +1 -0
- package/dist/src/common/plural.js +46 -0
- package/dist/src/common/plural.js.map +1 -0
- package/dist/src/common/report.d.ts +72 -14
- package/dist/src/common/report.d.ts.map +1 -1
- package/dist/src/common/report.js +146 -22
- package/dist/src/common/report.js.map +1 -1
- package/dist/src/common/temporal.d.ts +22 -5
- package/dist/src/common/temporal.d.ts.map +1 -1
- package/dist/src/common/temporal.js +13 -2
- package/dist/src/common/temporal.js.map +1 -1
- package/dist/src/common/text.d.ts +39 -23
- package/dist/src/common/text.d.ts.map +1 -1
- package/dist/src/common/text.js +51 -21
- package/dist/src/common/text.js.map +1 -1
- package/dist/src/common/weights.d.ts +37 -4
- package/dist/src/common/weights.d.ts.map +1 -1
- package/dist/src/common/weights.js +61 -2
- package/dist/src/common/weights.js.map +1 -1
- package/dist/src/common/writer.d.ts +19 -3
- package/dist/src/common/writer.d.ts.map +1 -1
- package/dist/src/common/writer.js +30 -2
- package/dist/src/common/writer.js.map +1 -1
- package/dist/src/common/xml.d.ts +125 -9
- package/dist/src/common/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +400 -83
- package/dist/src/common/xml.js.map +1 -1
- package/dist/src/common/zip.d.ts +44 -4
- package/dist/src/common/zip.d.ts.map +1 -1
- package/dist/src/common/zip.js +134 -5
- package/dist/src/common/zip.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts +80 -24
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +145 -32
- package/dist/src/formats/csv/exporter.js.map +1 -1
- package/dist/src/formats/csv/header.d.ts +38 -9
- package/dist/src/formats/csv/header.d.ts.map +1 -1
- package/dist/src/formats/csv/header.js +46 -10
- package/dist/src/formats/csv/header.js.map +1 -1
- package/dist/src/formats/csv/importer.d.ts +187 -37
- package/dist/src/formats/csv/importer.d.ts.map +1 -1
- package/dist/src/formats/csv/importer.js +835 -204
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/index.d.ts +100 -19
- package/dist/src/formats/csv/index.d.ts.map +1 -1
- package/dist/src/formats/csv/index.js +94 -21
- package/dist/src/formats/csv/index.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts +95 -10
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +333 -122
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/csv/values.d.ts +1 -0
- package/dist/src/formats/csv/values.d.ts.map +1 -1
- package/dist/src/formats/csv/values.js +1 -0
- package/dist/src/formats/csv/values.js.map +1 -1
- package/dist/src/formats/cx/exporter.d.ts +102 -0
- package/dist/src/formats/cx/exporter.d.ts.map +1 -0
- package/dist/src/formats/cx/exporter.js +1217 -0
- package/dist/src/formats/cx/exporter.js.map +1 -0
- package/dist/src/formats/cx/importer.d.ts +79 -32
- package/dist/src/formats/cx/importer.d.ts.map +1 -1
- package/dist/src/formats/cx/importer.js +680 -285
- package/dist/src/formats/cx/importer.js.map +1 -1
- package/dist/src/formats/cx/index.d.ts +4 -3
- package/dist/src/formats/cx/index.d.ts.map +1 -1
- package/dist/src/formats/cx/index.js +4 -3
- package/dist/src/formats/cx/index.js.map +1 -1
- package/dist/src/formats/cx2/exporter.d.ts +29 -11
- package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
- package/dist/src/formats/cx2/exporter.js +39 -268
- package/dist/src/formats/cx2/exporter.js.map +1 -1
- package/dist/src/formats/cx2/importer.d.ts +78 -28
- package/dist/src/formats/cx2/importer.d.ts.map +1 -1
- package/dist/src/formats/cx2/importer.js +226 -79
- package/dist/src/formats/cx2/importer.js.map +1 -1
- package/dist/src/formats/cx2/index.d.ts +2 -1
- package/dist/src/formats/cx2/index.d.ts.map +1 -1
- package/dist/src/formats/cx2/index.js +2 -1
- package/dist/src/formats/cx2/index.js.map +1 -1
- package/dist/src/formats/cys/constants.d.ts +66 -17
- package/dist/src/formats/cys/constants.d.ts.map +1 -1
- package/dist/src/formats/cys/constants.js +59 -18
- package/dist/src/formats/cys/constants.js.map +1 -1
- package/dist/src/formats/cys/exporter.d.ts +118 -0
- package/dist/src/formats/cys/exporter.d.ts.map +1 -0
- package/dist/src/formats/cys/exporter.js +1072 -0
- package/dist/src/formats/cys/exporter.js.map +1 -0
- package/dist/src/formats/cys/importer.d.ts +22 -8
- package/dist/src/formats/cys/importer.d.ts.map +1 -1
- package/dist/src/formats/cys/importer.js +510 -149
- package/dist/src/formats/cys/importer.js.map +1 -1
- package/dist/src/formats/cys/index.d.ts +4 -2
- package/dist/src/formats/cys/index.d.ts.map +1 -1
- package/dist/src/formats/cys/index.js +4 -2
- package/dist/src/formats/cys/index.js.map +1 -1
- package/dist/src/formats/cys/session.d.ts +46 -7
- package/dist/src/formats/cys/session.d.ts.map +1 -1
- package/dist/src/formats/cys/session.js +98 -24
- package/dist/src/formats/cys/session.js.map +1 -1
- package/dist/src/formats/cys/tables.d.ts +9 -2
- package/dist/src/formats/cys/tables.d.ts.map +1 -1
- package/dist/src/formats/cys/tables.js +29 -7
- package/dist/src/formats/cys/tables.js.map +1 -1
- package/dist/src/formats/dot/exporter.d.ts +42 -14
- package/dist/src/formats/dot/exporter.d.ts.map +1 -1
- package/dist/src/formats/dot/exporter.js +56 -29
- package/dist/src/formats/dot/exporter.js.map +1 -1
- package/dist/src/formats/dot/importer.d.ts +91 -29
- package/dist/src/formats/dot/importer.d.ts.map +1 -1
- package/dist/src/formats/dot/importer.js +190 -39
- package/dist/src/formats/dot/importer.js.map +1 -1
- package/dist/src/formats/dot/index.d.ts +2 -1
- package/dist/src/formats/dot/index.d.ts.map +1 -1
- package/dist/src/formats/dot/index.js +2 -1
- package/dist/src/formats/dot/index.js.map +1 -1
- package/dist/src/formats/dot/names.d.ts +44 -11
- package/dist/src/formats/dot/names.d.ts.map +1 -1
- package/dist/src/formats/dot/names.js +44 -11
- package/dist/src/formats/dot/names.js.map +1 -1
- package/dist/src/formats/dot/tokenizer.d.ts +9 -2
- package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
- package/dist/src/formats/dot/tokenizer.js +20 -5
- package/dist/src/formats/dot/tokenizer.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts +46 -15
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +45 -15
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts +87 -23
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +471 -90
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gexf/index.d.ts +84 -21
- package/dist/src/formats/gexf/index.d.ts.map +1 -1
- package/dist/src/formats/gexf/index.js +80 -23
- package/dist/src/formats/gexf/index.js.map +1 -1
- package/dist/src/formats/gexf/schema.d.ts +67 -15
- package/dist/src/formats/gexf/schema.d.ts.map +1 -1
- package/dist/src/formats/gexf/schema.js +59 -13
- package/dist/src/formats/gexf/schema.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts +69 -21
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +74 -29
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts +116 -25
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +290 -61
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/index.d.ts +87 -24
- package/dist/src/formats/gml/index.d.ts.map +1 -1
- package/dist/src/formats/gml/index.js +79 -26
- package/dist/src/formats/gml/index.js.map +1 -1
- package/dist/src/formats/gml/syntax.d.ts +67 -18
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +108 -26
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/constants.d.ts +174 -44
- package/dist/src/formats/graphml/constants.d.ts.map +1 -1
- package/dist/src/formats/graphml/constants.js +165 -44
- package/dist/src/formats/graphml/constants.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts +17 -9
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +33 -12
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts +19 -7
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +466 -39
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/index.d.ts +2 -1
- package/dist/src/formats/graphml/index.d.ts.map +1 -1
- package/dist/src/formats/graphml/index.js +2 -1
- package/dist/src/formats/graphml/index.js.map +1 -1
- package/dist/src/formats/graphml/tree.d.ts +6 -2
- package/dist/src/formats/graphml/tree.d.ts.map +1 -1
- package/dist/src/formats/graphml/tree.js +6 -2
- package/dist/src/formats/graphml/tree.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts +8 -3
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +36 -15
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/dialect.d.ts +88 -24
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +109 -19
- package/dist/src/formats/json/dialect.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts +134 -22
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +135 -54
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts +173 -52
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +899 -220
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/index.d.ts +3 -2
- package/dist/src/formats/json/index.d.ts.map +1 -1
- package/dist/src/formats/json/index.js +3 -2
- package/dist/src/formats/json/index.js.map +1 -1
- package/dist/src/formats/json/obographs-export.d.ts +68 -0
- package/dist/src/formats/json/obographs-export.d.ts.map +1 -0
- package/dist/src/formats/json/obographs-export.js +941 -0
- package/dist/src/formats/json/obographs-export.js.map +1 -0
- package/dist/src/formats/json/obographs.d.ts +26 -0
- package/dist/src/formats/json/obographs.d.ts.map +1 -1
- package/dist/src/formats/json/obographs.js +297 -80
- package/dist/src/formats/json/obographs.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts +109 -21
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +238 -85
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/neo4j/header.d.ts +17 -4
- package/dist/src/formats/neo4j/header.d.ts.map +1 -1
- package/dist/src/formats/neo4j/header.js +36 -4
- package/dist/src/formats/neo4j/header.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts +107 -27
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +415 -63
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/neo4j/index.d.ts +73 -23
- package/dist/src/formats/neo4j/index.d.ts.map +1 -1
- package/dist/src/formats/neo4j/index.js +65 -25
- package/dist/src/formats/neo4j/index.js.map +1 -1
- package/dist/src/formats/obo/exporter.d.ts +150 -0
- package/dist/src/formats/obo/exporter.d.ts.map +1 -0
- package/dist/src/formats/obo/exporter.js +1736 -0
- package/dist/src/formats/obo/exporter.js.map +1 -0
- package/dist/src/formats/obo/importer.d.ts +95 -19
- package/dist/src/formats/obo/importer.d.ts.map +1 -1
- package/dist/src/formats/obo/importer.js +475 -104
- package/dist/src/formats/obo/importer.js.map +1 -1
- package/dist/src/formats/obo/index.d.ts +6 -3
- package/dist/src/formats/obo/index.d.ts.map +1 -1
- package/dist/src/formats/obo/index.js +6 -3
- package/dist/src/formats/obo/index.js.map +1 -1
- package/dist/src/formats/obo/syntax.d.ts +108 -4
- package/dist/src/formats/obo/syntax.d.ts.map +1 -1
- package/dist/src/formats/obo/syntax.js +211 -34
- package/dist/src/formats/obo/syntax.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts +54 -16
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +66 -34
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts +84 -22
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +303 -83
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/index.d.ts +2 -1
- package/dist/src/formats/pajek/index.d.ts.map +1 -1
- package/dist/src/formats/pajek/index.js +2 -1
- package/dist/src/formats/pajek/index.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts +77 -14
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +183 -51
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/columns.d.ts +1 -0
- package/dist/src/formats/xgmml/columns.d.ts.map +1 -1
- package/dist/src/formats/xgmml/columns.js +4 -0
- package/dist/src/formats/xgmml/columns.js.map +1 -1
- package/dist/src/formats/xgmml/constants.d.ts +186 -56
- package/dist/src/formats/xgmml/constants.d.ts.map +1 -1
- package/dist/src/formats/xgmml/constants.js +181 -57
- package/dist/src/formats/xgmml/constants.js.map +1 -1
- package/dist/src/formats/xgmml/document.d.ts +65 -11
- package/dist/src/formats/xgmml/document.d.ts.map +1 -1
- package/dist/src/formats/xgmml/document.js +157 -28
- package/dist/src/formats/xgmml/document.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +61 -3
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +129 -26
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/exporter.d.ts +13 -6
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
- package/dist/src/formats/xgmml/exporter.js +20 -21
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/importer.d.ts +33 -12
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
- package/dist/src/formats/xgmml/importer.js +35 -72
- package/dist/src/formats/xgmml/importer.js.map +1 -1
- package/dist/src/formats/xgmml/index.d.ts +1 -0
- package/dist/src/formats/xgmml/index.d.ts.map +1 -1
- package/dist/src/formats/xgmml/index.js +1 -0
- package/dist/src/formats/xgmml/index.js.map +1 -1
- package/dist/src/formats/xgmml/values.d.ts +8 -1
- package/dist/src/formats/xgmml/values.d.ts.map +1 -1
- package/dist/src/formats/xgmml/values.js +7 -2
- package/dist/src/formats/xgmml/values.js.map +1 -1
- package/dist/src/index.d.ts +23 -18
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +28 -16
- package/dist/src/index.js.map +1 -1
- package/dist/src/registry.d.ts +478 -92
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +807 -88
- package/dist/src/registry.js.map +1 -1
- package/dist/src/sniff.d.ts +61 -19
- package/dist/src/sniff.d.ts.map +1 -1
- package/dist/src/sniff.js +121 -29
- package/dist/src/sniff.js.map +1 -1
- package/dist/src/types.d.ts +362 -112
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +28 -7
- package/dist/src/types.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/docs/samples/README.md +25 -0
- package/docs/samples/got-edges.csv +353 -0
- package/docs/samples/got-network.graphml +1384 -0
- package/docs/samples/got-nodes.csv +108 -0
- package/docs/samples/got.cx +930 -0
- package/docs/samples/got.cx2 +465 -0
- package/docs/samples/got.gexf +471 -0
- package/docs/samples/got.gml +2650 -0
- package/docs/samples/got.json +2546 -0
- package/docs/samples/got.net +461 -0
- package/docs/samples/movies-nodes.csv +9 -0
- package/docs/samples/movies-rels.csv +8 -0
- package/docs/samples/networks.cys +0 -0
- package/docs/samples/proteins.xgmml +9 -0
- package/docs/samples/teams.gv +24 -0
- package/docs/samples/vehicles.obo +25 -0
- package/package.json +9 -4
- package/src/children.ts +18 -6
- package/src/common/attributes.ts +29 -11
- package/src/common/cell-budget.ts +11 -3
- package/src/common/choose.ts +89 -0
- package/src/common/codes.ts +490 -66
- package/src/common/compare.ts +472 -0
- package/src/common/cx-export.ts +432 -0
- package/src/common/declared-types.ts +38 -4
- package/src/common/direction.ts +78 -51
- package/src/common/escape.ts +33 -17
- package/src/common/export.ts +284 -47
- package/src/common/format.ts +30 -1
- package/src/common/ids.ts +14 -3
- package/src/common/input.ts +1057 -134
- package/src/common/json-elements.ts +1034 -198
- package/src/common/line-format.ts +249 -0
- package/src/common/lists.ts +6 -1
- package/src/common/ontology-export.ts +461 -0
- package/src/common/ontology.ts +31 -9
- package/src/common/options.ts +110 -15
- package/src/common/plural.ts +49 -0
- package/src/common/report.ts +168 -25
- package/src/common/temporal.ts +22 -5
- package/src/common/text.ts +62 -23
- package/src/common/weights.ts +74 -4
- package/src/common/writer.ts +37 -3
- package/src/common/xml.ts +477 -88
- package/src/common/zip.ts +169 -7
- package/src/formats/csv/exporter.ts +201 -46
- package/src/formats/csv/header.ts +50 -11
- package/src/formats/csv/importer.ts +1133 -245
- package/src/formats/csv/index.ts +106 -19
- package/src/formats/csv/records.ts +396 -122
- package/src/formats/csv/values.ts +1 -0
- package/src/formats/cx/exporter.ts +1483 -0
- package/src/formats/cx/importer.ts +888 -300
- package/src/formats/cx/index.ts +4 -3
- package/src/formats/cx2/exporter.ts +61 -338
- package/src/formats/cx2/importer.ts +317 -86
- package/src/formats/cx2/index.ts +2 -1
- package/src/formats/cys/constants.ts +62 -17
- package/src/formats/cys/exporter.ts +1368 -0
- package/src/formats/cys/importer.ts +659 -157
- package/src/formats/cys/index.ts +4 -2
- package/src/formats/cys/session.ts +139 -31
- package/src/formats/cys/tables.ts +47 -9
- package/src/formats/dot/exporter.ts +83 -37
- package/src/formats/dot/importer.ts +248 -54
- package/src/formats/dot/index.ts +2 -1
- package/src/formats/dot/names.ts +44 -11
- package/src/formats/dot/tokenizer.ts +25 -6
- package/src/formats/gexf/exporter.ts +57 -18
- package/src/formats/gexf/importer.ts +636 -105
- package/src/formats/gexf/index.ts +90 -21
- package/src/formats/gexf/schema.ts +67 -15
- package/src/formats/gml/exporter.ts +90 -36
- package/src/formats/gml/importer.ts +388 -67
- package/src/formats/gml/index.ts +85 -24
- package/src/formats/gml/syntax.ts +111 -26
- package/src/formats/graphml/constants.ts +178 -44
- package/src/formats/graphml/exporter.ts +52 -18
- package/src/formats/graphml/importer.ts +640 -60
- package/src/formats/graphml/index.ts +2 -1
- package/src/formats/graphml/tree.ts +6 -2
- package/src/formats/graphml/yfiles.ts +37 -15
- package/src/formats/json/dialect.ts +128 -29
- package/src/formats/json/exporter.ts +205 -68
- package/src/formats/json/importer.ts +1163 -253
- package/src/formats/json/index.ts +3 -1
- package/src/formats/json/obographs-export.ts +1197 -0
- package/src/formats/json/obographs.ts +353 -92
- package/src/formats/neo4j/exporter.ts +320 -101
- package/src/formats/neo4j/header.ts +44 -6
- package/src/formats/neo4j/importer.ts +512 -73
- package/src/formats/neo4j/index.ts +75 -23
- package/src/formats/obo/exporter.ts +2210 -0
- package/src/formats/obo/importer.ts +622 -109
- package/src/formats/obo/index.ts +6 -3
- package/src/formats/obo/syntax.ts +232 -37
- package/src/formats/pajek/exporter.ts +89 -41
- package/src/formats/pajek/importer.ts +407 -93
- package/src/formats/pajek/index.ts +2 -1
- package/src/formats/pajek/syntax.ts +213 -52
- package/src/formats/xgmml/columns.ts +10 -0
- package/src/formats/xgmml/constants.ts +183 -56
- package/src/formats/xgmml/document.ts +212 -34
- package/src/formats/xgmml/emit.ts +175 -34
- package/src/formats/xgmml/exporter.ts +35 -27
- package/src/formats/xgmml/importer.ts +64 -86
- package/src/formats/xgmml/index.ts +1 -0
- package/src/formats/xgmml/values.ts +11 -3
- package/src/index.ts +81 -11
- package/src/registry.ts +1121 -135
- package/src/sniff.ts +163 -32
- package/src/types.ts +385 -114
- package/dist/chunks/escape-B-tkk_Cl.js.map +0 -1
- package/dist/chunks/export-Bh60Dv-n.js +0 -582
- package/dist/chunks/export-Bh60Dv-n.js.map +0 -1
- package/dist/chunks/exporter-BGrsWamJ.js.map +0 -1
- package/dist/chunks/exporter-DIeGJXAZ.js.map +0 -1
- package/dist/chunks/importer-BWY2FFc5.js.map +0 -1
- package/dist/chunks/importer-DD-xv9_Y.js +0 -1471
- package/dist/chunks/importer-DD-xv9_Y.js.map +0 -1
- package/dist/chunks/importer-DET1tg6s.js.map +0 -1
- package/dist/chunks/importer-DEcKhsmQ.js +0 -1614
- package/dist/chunks/importer-DEcKhsmQ.js.map +0 -1
- package/dist/chunks/importer-DIrbbnAf.js.map +0 -1
- package/dist/chunks/importer-D_e7e7LX.js.map +0 -1
- package/dist/chunks/importer-eUREhBGj.js.map +0 -1
- package/dist/chunks/importer-vi3bSdtw.js.map +0 -1
- package/dist/chunks/importer-zGLo8Dg_.js.map +0 -1
- package/dist/chunks/json-elements-DDS8N2Dd.js +0 -779
- package/dist/chunks/json-elements-DDS8N2Dd.js.map +0 -1
- package/dist/chunks/ontology-BnrJ4I98.js +0 -113
- package/dist/chunks/ontology-BnrJ4I98.js.map +0 -1
- package/dist/chunks/records-dbRkxwaq.js.map +0 -1
- package/dist/chunks/report-B1z4WT9e.js +0 -2372
- package/dist/chunks/report-B1z4WT9e.js.map +0 -1
- package/dist/chunks/text-Dr0Ifpag.js.map +0 -1
- package/dist/chunks/weights-Dzba96G3.js +0 -176
- package/dist/chunks/weights-Dzba96G3.js.map +0 -1
- package/dist/chunks/writer-C7flM-Ih.js.map +0 -1
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* groups, nested-network pointers); the subnetwork's CyCSV tables give its columns (LOCAL_ATTRS as
|
|
10
10
|
* they are, the SHARED_ATTRS columns `cytables.xml` joins in, HIDDEN and app tables as hidden
|
|
11
11
|
* columns under their namespace); its first view gives positions (y flipped) and the `graphics`
|
|
12
|
-
* column, further views `position@<n>` columns.
|
|
12
|
+
* column, further views `position@<n>` columns. A table's `name` is the label (what Cytoscape
|
|
13
|
+
* shows), in a label column named `name`, and ids an exporter mangled are restored from the node
|
|
14
|
+
* table's `graphty:originalId` (restoreMangledIds). 2.x: one full XGMML file per network, with
|
|
13
15
|
* selection and hidden state from `cysession.xml`. Everything is resolved by the XGMML emitter, so
|
|
14
16
|
* both readers share every column rule. Styles are not read (issue #706): W_STYLES_NOT_IMPORTED.
|
|
15
17
|
*/
|
|
@@ -18,15 +20,17 @@ import { GraphFormatError, type GraphSink, INVALID_INDEX } from "@graphty/graph-
|
|
|
18
20
|
|
|
19
21
|
import { declareResolved } from "../../common/attributes.js";
|
|
20
22
|
import { IdCoercer } from "../../common/ids.js";
|
|
21
|
-
import { decodeEntryName,
|
|
23
|
+
import { decodeEntryName, throwIfAborted } from "../../common/input.js";
|
|
22
24
|
import {
|
|
23
25
|
chooseGraph,
|
|
26
|
+
graphChosen,
|
|
24
27
|
type ImportFormatDefaults,
|
|
25
28
|
reportSinkOptions,
|
|
26
29
|
reportUnusedOptions,
|
|
27
30
|
type ResolvedImportOptions,
|
|
28
31
|
resolveImportOptions,
|
|
29
32
|
} from "../../common/options.js";
|
|
33
|
+
import { agree, elementCount, plural } from "../../common/plural.js";
|
|
30
34
|
import { ImportReportBuilder } from "../../common/report.js";
|
|
31
35
|
import { startsLikeZip } from "../../common/zip.js";
|
|
32
36
|
import {
|
|
@@ -38,6 +42,7 @@ import {
|
|
|
38
42
|
type ImportInput,
|
|
39
43
|
type ImportReport,
|
|
40
44
|
} from "../../types.js";
|
|
45
|
+
import { ORIGINAL_ID_ATTRIBUTE } from "../graphml/constants.js";
|
|
41
46
|
import { XGMML_ISSUE, XGMML_ORIGIN_NAMESPACE } from "../xgmml/constants.js";
|
|
42
47
|
import {
|
|
43
48
|
type AttRec,
|
|
@@ -52,6 +57,7 @@ import {
|
|
|
52
57
|
dialectOf,
|
|
53
58
|
type EmitExtras,
|
|
54
59
|
membersOf,
|
|
60
|
+
parseCoordinate,
|
|
55
61
|
XgmmlEmitter,
|
|
56
62
|
type XgmmlSettings,
|
|
57
63
|
} from "../xgmml/emit.js";
|
|
@@ -70,9 +76,11 @@ import {
|
|
|
70
76
|
} from "./constants.js";
|
|
71
77
|
import {
|
|
72
78
|
elementsNamed,
|
|
79
|
+
entryLabel,
|
|
73
80
|
listed,
|
|
74
81
|
type NetworkEntry,
|
|
75
82
|
openSession,
|
|
83
|
+
parseXmlTree,
|
|
76
84
|
readXmlTree,
|
|
77
85
|
type Session,
|
|
78
86
|
type SessionEntry,
|
|
@@ -82,22 +90,33 @@ import {
|
|
|
82
90
|
} from "./session.js";
|
|
83
91
|
import { cellAtt, type CyTable, readCyTable, type VirtualColumn, virtualColumnsOf } from "./tables.js";
|
|
84
92
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The format-specific options of the session importer.
|
|
95
|
+
* @category Built-in formats
|
|
96
|
+
*/
|
|
97
|
+
export interface CysImportOptions extends GraphChoiceOptions, CommonImportOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Where Cytoscape's `z` value (a drawing order, not a depth) goes: "column" keeps it as a node
|
|
100
|
+
* attribute named `z`; "position" makes it the third coordinate of the position.
|
|
101
|
+
* @defaultValue "column"
|
|
102
|
+
*/
|
|
88
103
|
zAs?: "column" | "position" | undefined;
|
|
89
104
|
/**
|
|
90
|
-
* The most bytes one import may
|
|
91
|
-
*
|
|
105
|
+
* The most bytes one import may unpack from the session archive, in total (2 GiB). A file that
|
|
106
|
+
* would unpack to more, or one compressed more than 1000 to 1, fails with E_TOO_LARGE.
|
|
107
|
+
* @defaultValue 2147483648
|
|
92
108
|
*/
|
|
93
109
|
maxUncompressedBytes?: number | undefined;
|
|
94
110
|
}
|
|
95
111
|
|
|
96
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* The per-format defaults: SUIDs as written, edges directed unless they say otherwise, the edge
|
|
114
|
+
* `weight` column as the weight (as for XGMML, the format of the session's networks).
|
|
115
|
+
*/
|
|
97
116
|
const FORMAT_DEFAULTS: ImportFormatDefaults = {
|
|
98
117
|
ids: "keep",
|
|
99
118
|
defaultDirected: true,
|
|
100
|
-
weightFrom:
|
|
119
|
+
weightFrom: "weight",
|
|
101
120
|
addMissingNodes: false,
|
|
102
121
|
};
|
|
103
122
|
|
|
@@ -112,6 +131,7 @@ const USED_OPTIONS: ReadonlySet<keyof CommonImportOptions> = new Set<keyof Commo
|
|
|
112
131
|
"weightFrom",
|
|
113
132
|
"weightDtype",
|
|
114
133
|
"long",
|
|
134
|
+
"restoreMangledIds",
|
|
115
135
|
"errorLimit",
|
|
116
136
|
"signal",
|
|
117
137
|
"onProgress",
|
|
@@ -124,6 +144,8 @@ type Choice = Choice3 | Choice2;
|
|
|
124
144
|
interface Choice3 {
|
|
125
145
|
readonly era: "3";
|
|
126
146
|
readonly network: NetworkEntry;
|
|
147
|
+
/** The subnetwork itself (two subnetworks can share an id). */
|
|
148
|
+
readonly graph: GraphRec;
|
|
127
149
|
readonly graphId: string;
|
|
128
150
|
readonly name: string | null;
|
|
129
151
|
readonly nodes: number;
|
|
@@ -225,23 +247,72 @@ async function parseEntry(
|
|
|
225
247
|
report: ImportReportBuilder,
|
|
226
248
|
inner: ResolvedImportOptions,
|
|
227
249
|
): Promise<Parsed> {
|
|
228
|
-
|
|
229
|
-
|
|
250
|
+
return (await parseOptionalEntry(session, entry, report, inner, false)) as Parsed;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Read an XGMML entry, its issues and counts relayed into the import's report (the entry name
|
|
255
|
+
* in each message), parsing within what is left of the import's error limit. A fatal issue fails
|
|
256
|
+
* the import, unless the entry is optional (a view): then it is recorded as an error and the
|
|
257
|
+
* entry is skipped (null).
|
|
258
|
+
* @param session - the session
|
|
259
|
+
* @param entry - the entry
|
|
260
|
+
* @param report - the import's report
|
|
261
|
+
* @param inner - the options entries are read with
|
|
262
|
+
* @param optional - whether the import can go on without the entry
|
|
263
|
+
* @returns the document and its dialect, or null for an optional entry that cannot be read
|
|
264
|
+
*/
|
|
265
|
+
async function parseOptionalEntry(
|
|
266
|
+
session: Session,
|
|
267
|
+
entry: SessionEntry,
|
|
268
|
+
report: ImportReportBuilder,
|
|
269
|
+
inner: ResolvedImportOptions,
|
|
270
|
+
optional: boolean,
|
|
271
|
+
): Promise<Parsed | null> {
|
|
272
|
+
const scratch = new ImportReportBuilder(FORMAT, Math.max(0, report.errorLimit - report.errorCount));
|
|
230
273
|
try {
|
|
274
|
+
// a damaged entry (bad CRC, truncated deflate) fails the scratch report like a parse error
|
|
275
|
+
const bytes = await session.read(entry, scratch);
|
|
231
276
|
const doc = await parseXgmml(bytes, scratch, inner, undefined);
|
|
232
277
|
const dialect = dialectOf(doc, scratch);
|
|
233
278
|
relay(scratch.issues, report, entry.name);
|
|
279
|
+
addCounts(scratch, report);
|
|
234
280
|
return { doc, dialect };
|
|
235
281
|
} catch (err) {
|
|
236
282
|
if (!(err instanceof ImportError)) {
|
|
237
283
|
throw err;
|
|
238
284
|
}
|
|
285
|
+
addCounts(scratch, report);
|
|
239
286
|
const { issues } = scratch;
|
|
287
|
+
if (scratch.truncated) {
|
|
288
|
+
// the entry reached the import's error limit: relaying its errors aborts the import
|
|
289
|
+
relay(issues, report, entry.name);
|
|
290
|
+
}
|
|
240
291
|
const fatal = issues.at(-1);
|
|
241
292
|
relay(issues.slice(0, -1), report, entry.name);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
293
|
+
const message = `${entryLabel(entry.name)}: ${fatal?.message ?? err.message}`;
|
|
294
|
+
const where = { line: fatal?.line ?? null };
|
|
295
|
+
if (fatal?.code === CYS_ISSUE.TOO_LARGE) {
|
|
296
|
+
// the byte budget is a limit of the whole import, not damage to one entry
|
|
297
|
+
report.error("unsupported", fatal.code, message);
|
|
298
|
+
throw report.abort(message, { code: fatal.code });
|
|
299
|
+
}
|
|
300
|
+
if (optional) {
|
|
301
|
+
report.error("parse-error", fatal?.code ?? CYS_ISSUE.CORRUPT, `${message}; the entry is not read`, where);
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
return report.fail(fatal?.code ?? CYS_ISSUE.CORRUPT, message, where);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Add an entry's element counts (the nodes skipped while parsing it) to the import's.
|
|
310
|
+
* @param from - the entry's report
|
|
311
|
+
* @param to - the import's report
|
|
312
|
+
*/
|
|
313
|
+
function addCounts(from: ImportReportBuilder, to: ImportReportBuilder): void {
|
|
314
|
+
for (const key of Object.keys(from.counts) as (keyof typeof from.counts)[]) {
|
|
315
|
+
to.counts[key] += from.counts[key];
|
|
245
316
|
}
|
|
246
317
|
}
|
|
247
318
|
|
|
@@ -255,9 +326,9 @@ function relay(issues: ImportReport["issues"], report: ImportReportBuilder, entr
|
|
|
255
326
|
for (const issue of issues) {
|
|
256
327
|
const where = { line: issue.line, element: issue.element };
|
|
257
328
|
if (issue.severity === "error") {
|
|
258
|
-
report.error(issue.category, issue.code, `${entry}: ${issue.message}`, where);
|
|
329
|
+
report.error(issue.category, issue.code, `${entryLabel(entry)}: ${issue.message}`, where);
|
|
259
330
|
} else {
|
|
260
|
-
report.warning(issue.category, issue.code, `${entry}: ${issue.message}`, where);
|
|
331
|
+
report.warning(issue.category, issue.code, `${entryLabel(entry)}: ${issue.message}`, where);
|
|
261
332
|
}
|
|
262
333
|
}
|
|
263
334
|
}
|
|
@@ -282,13 +353,31 @@ async function networks3(
|
|
|
282
353
|
const choices: Choice3[] = [];
|
|
283
354
|
for (const network of layout.networks) {
|
|
284
355
|
const parsed = await parseEntry(session, network, report, inner);
|
|
356
|
+
if (parsed.dialect.view) {
|
|
357
|
+
report.fail(
|
|
358
|
+
XGMML_ISSUE.VIEW_DOCUMENT,
|
|
359
|
+
`${network.name}: this is a session view file (cy:view), not a network file; it holds no topology`,
|
|
360
|
+
{ line: parsed.doc.root.line },
|
|
361
|
+
);
|
|
362
|
+
}
|
|
285
363
|
docs.set(network.name, parsed);
|
|
286
364
|
for (const graph of registeredGraphs(parsed)) {
|
|
365
|
+
if (graph.id === null) {
|
|
366
|
+
const label = graph.label === null ? "" : ` ("${graph.label}")`;
|
|
367
|
+
report.error(
|
|
368
|
+
"missing-value",
|
|
369
|
+
XGMML_ISSUE.MISSING_ID,
|
|
370
|
+
`${network.name}: a registered subnetwork${label} has no id; its tables and views cannot be found and it is skipped`,
|
|
371
|
+
{ line: graph.line },
|
|
372
|
+
);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
287
375
|
const members = membersOf(parsed.doc, graph);
|
|
288
376
|
choices.push({
|
|
289
377
|
era: "3",
|
|
290
378
|
network,
|
|
291
|
-
|
|
379
|
+
graph,
|
|
380
|
+
graphId: graph.id,
|
|
292
381
|
name: graph.label ?? graph.id,
|
|
293
382
|
nodes: new Set(members.nodes).size,
|
|
294
383
|
edges: members.edges.length,
|
|
@@ -307,13 +396,27 @@ async function networks3(
|
|
|
307
396
|
report.warning(
|
|
308
397
|
"validation-error",
|
|
309
398
|
CYS_ISSUE.DANGLING_REFERENCE,
|
|
310
|
-
`${strayViews.length + strayTables.length} view(
|
|
399
|
+
`${strayViews.length + strayTables.length} view${plural(strayViews.length + strayTables.length)} or table${plural(strayViews.length + strayTables.length)} ${agree(strayViews.length + strayTables.length, "belongs", "belong")} to a network the session does not hold and were not read: ${listed([...strayViews, ...strayTables.map((t) => t.name)])}`,
|
|
311
400
|
);
|
|
312
401
|
}
|
|
313
402
|
if (layout.networkList === null) {
|
|
314
403
|
return choices;
|
|
315
404
|
}
|
|
316
|
-
const tree = await
|
|
405
|
+
const tree = await parseXmlTree(
|
|
406
|
+
await session.read(layout.networkList, report),
|
|
407
|
+
layout.networkList.name,
|
|
408
|
+
report,
|
|
409
|
+
inner,
|
|
410
|
+
);
|
|
411
|
+
if (typeof tree === "string") {
|
|
412
|
+
// the list only orders the networks: without it they keep the order of their entries
|
|
413
|
+
report.warning(
|
|
414
|
+
"unsupported",
|
|
415
|
+
CYS_ISSUE.ENTRY_SKIPPED,
|
|
416
|
+
`${layout.networkList.name}: ${tree}; it is not read and the networks are listed in entry order`,
|
|
417
|
+
);
|
|
418
|
+
return choices;
|
|
419
|
+
}
|
|
317
420
|
const order = new Map<string, number>();
|
|
318
421
|
for (const node of elementsNamed(tree, "network")) {
|
|
319
422
|
const id = node.attrs.get("id");
|
|
@@ -355,7 +458,9 @@ function isGroupTable(table: TableEntry, session: Session, docs: ReadonlyMap<str
|
|
|
355
458
|
* @returns the graphs
|
|
356
459
|
*/
|
|
357
460
|
function registeredGraphs(parsed: Parsed): GraphRec[] {
|
|
358
|
-
|
|
461
|
+
const subnetworks = parsed.doc.root.subgraphs.filter((g) => isCyTrue(g.registered));
|
|
462
|
+
// a registered root with no subnetworks (another tool's network file) is the network itself
|
|
463
|
+
return subnetworks.length === 0 && isCyTrue(parsed.doc.root.registered) ? [parsed.doc.root] : subnetworks;
|
|
359
464
|
}
|
|
360
465
|
|
|
361
466
|
/**
|
|
@@ -366,100 +471,88 @@ function registeredGraphs(parsed: Parsed): GraphRec[] {
|
|
|
366
471
|
* @param parsed - its network file, parsed for this import (its records are filled in)
|
|
367
472
|
*/
|
|
368
473
|
async function import3(prepared: Prepared, choice: Choice3, sink: GraphSink, parsed: Parsed): Promise<void> {
|
|
369
|
-
const {
|
|
370
|
-
const
|
|
371
|
-
const { doc, dialect } = parsed;
|
|
372
|
-
const graph = doc.root.subgraphs.find((g) => g.id === choice.graphId) as GraphRec;
|
|
373
|
-
const members = membersOf(doc, graph);
|
|
474
|
+
const { graph } = choice;
|
|
475
|
+
const members = membersOf(parsed.doc, graph);
|
|
374
476
|
const nodes = byId(members.nodes);
|
|
375
477
|
const edges = byId(members.edges);
|
|
376
|
-
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
for (const [key, cells] of table.rows) {
|
|
414
|
-
const atts = target(key);
|
|
415
|
-
if (atts === undefined) {
|
|
416
|
-
if (!declared.has(key)) {
|
|
417
|
-
unmatched++;
|
|
418
|
-
}
|
|
419
|
-
continue;
|
|
420
|
-
}
|
|
421
|
-
const line = table.lines.get(key) ?? 0;
|
|
422
|
-
for (let i = 1; i < table.columns.length && i < cells.length; i++) {
|
|
423
|
-
const att = cellAtt(table.columns[i], cells[i], line, namespace, hidden);
|
|
424
|
-
if (att !== null) {
|
|
425
|
-
atts.push(att);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
478
|
+
// the records are shared by every network of the file: what this import adds is undone after
|
|
479
|
+
const restore = saveRecords(graph, nodes, edges);
|
|
480
|
+
try {
|
|
481
|
+
await fill3(prepared, choice, sink, parsed, nodes, edges);
|
|
482
|
+
} finally {
|
|
483
|
+
restore();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* The state of the records an import fills in (atts, coordinates, graphics), and a function that
|
|
489
|
+
* puts it back.
|
|
490
|
+
* @param graph - the subnetwork
|
|
491
|
+
* @param nodes - its node records
|
|
492
|
+
* @param edges - its edge records
|
|
493
|
+
* @returns the restore function
|
|
494
|
+
*/
|
|
495
|
+
function saveRecords(
|
|
496
|
+
graph: GraphRec,
|
|
497
|
+
nodes: ReadonlyMap<string, NodeRec>,
|
|
498
|
+
edges: ReadonlyMap<string, EdgeRec>,
|
|
499
|
+
): () => void {
|
|
500
|
+
const saved = [...nodes.values()].map((n) => ({
|
|
501
|
+
n,
|
|
502
|
+
atts: n.atts.length,
|
|
503
|
+
x: n.x,
|
|
504
|
+
y: n.y,
|
|
505
|
+
z: n.z,
|
|
506
|
+
graphics: n.graphics,
|
|
507
|
+
}));
|
|
508
|
+
const savedEdges = [...edges.values()].map((e) => ({ e, atts: e.atts.length, graphics: e.graphics }));
|
|
509
|
+
const graphAtts = graph.atts.length;
|
|
510
|
+
const graphGraphics = graph.graphics;
|
|
511
|
+
return (): void => {
|
|
512
|
+
for (const { n, atts, x, y, z, graphics } of saved) {
|
|
513
|
+
n.atts.length = atts;
|
|
514
|
+
Object.assign(n, { x, y, z, graphics });
|
|
428
515
|
}
|
|
429
|
-
for (const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
let ns = namespace;
|
|
433
|
-
if (local) {
|
|
434
|
-
ns = owned ? "SHARED_ATTRS" : null;
|
|
435
|
-
}
|
|
436
|
-
for (const [key, text] of virtual.values) {
|
|
437
|
-
const att = cellAtt(virtual.column, text, table.lines.get(key) ?? 0, ns, hidden);
|
|
438
|
-
if (att !== null) {
|
|
439
|
-
target(key)?.push(att);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
516
|
+
for (const { e, atts, graphics } of savedEdges) {
|
|
517
|
+
e.atts.length = atts;
|
|
518
|
+
e.graphics = graphics;
|
|
442
519
|
}
|
|
443
|
-
|
|
520
|
+
graph.atts.length = graphAtts;
|
|
521
|
+
graph.graphics = graphGraphics;
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Fill one subnetwork's records from its tables and views and emit it.
|
|
527
|
+
* @param prepared - the prepared import
|
|
528
|
+
* @param choice - the subnetwork
|
|
529
|
+
* @param sink - the sink
|
|
530
|
+
* @param parsed - its network file
|
|
531
|
+
* @param nodes - its node records by id
|
|
532
|
+
* @param edges - its edge records by id
|
|
533
|
+
*/
|
|
534
|
+
async function fill3(
|
|
535
|
+
prepared: Prepared,
|
|
536
|
+
choice: Choice3,
|
|
537
|
+
sink: GraphSink,
|
|
538
|
+
parsed: Parsed,
|
|
539
|
+
nodes: ReadonlyMap<string, NodeRec>,
|
|
540
|
+
edges: ReadonlyMap<string, EdgeRec>,
|
|
541
|
+
): Promise<void> {
|
|
542
|
+
const { report, inner } = prepared;
|
|
543
|
+
const { doc, dialect } = parsed;
|
|
544
|
+
const { graph } = choice;
|
|
545
|
+
const members = membersOf(doc, graph);
|
|
546
|
+
const unmatched = await applyTables(prepared, choice, doc, nodes, edges);
|
|
444
547
|
if (unmatched > 0) {
|
|
445
548
|
report.warning(
|
|
446
549
|
"parse-error",
|
|
447
550
|
CYS_ISSUE.TABLE_ROW,
|
|
448
|
-
`${unmatched} table row(
|
|
551
|
+
`${unmatched} table row${plural(unmatched)} of network ${choice.graphId} ${agree(unmatched, "names", "name")} no node, edge or network in it (left over from deleted elements) and ${agree(unmatched, "is", "are")} not read`,
|
|
449
552
|
);
|
|
450
553
|
}
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
const extraViews: Map<string, [number, number, number]>[] = [];
|
|
454
|
-
for (const [i, view] of views.entries()) {
|
|
455
|
-
const viewDoc = (await parseEntry(session, view, report, inner)).doc;
|
|
456
|
-
if (i === 0) {
|
|
457
|
-
visualStyle = viewDoc.root.attrs.get("cy:visualStyle") ?? null;
|
|
458
|
-
applyView(viewDoc, graph, nodes, edges, report, view.name);
|
|
459
|
-
} else {
|
|
460
|
-
extraViews.push(viewPositions(viewDoc, prepared.zAs));
|
|
461
|
-
}
|
|
462
|
-
}
|
|
554
|
+
const restoredIds = inner.restoreMangledIds ? restoreIds(members.nodes) : new Map<string, string>();
|
|
555
|
+
const { visualStyle, extraViews, extraIds } = await readViews(prepared, choice, nodes, edges);
|
|
463
556
|
const cyMeta = sessionMeta(prepared, {
|
|
464
557
|
collection: doc.root.label ?? choice.network.title,
|
|
465
558
|
network: choice.graphId,
|
|
@@ -473,40 +566,259 @@ async function import3(prepared: Prepared, choice: Choice3, sink: GraphSink, par
|
|
|
473
566
|
entry: choice.network.name,
|
|
474
567
|
graphName: localName(graph) ?? choice.name,
|
|
475
568
|
metaExtra: { [META_KEY]: cyMeta },
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
.flatMap((n) => (n.id === null ? [] : [n.id])),
|
|
480
|
-
),
|
|
569
|
+
labelFromName: true,
|
|
570
|
+
restoredIds,
|
|
571
|
+
groupNodes: groupNodeIds(members.nodes),
|
|
481
572
|
resolvePointer: pointerResolver(prepared, doc),
|
|
482
573
|
onMissingMember: (group, member): void => {
|
|
483
|
-
|
|
484
|
-
if (record === undefined) {
|
|
485
|
-
groups.push({ group, members: [member] });
|
|
486
|
-
} else if (!record.members.includes(member)) {
|
|
487
|
-
record.members.push(member);
|
|
488
|
-
}
|
|
574
|
+
addMissingMember(groups, group, member);
|
|
489
575
|
},
|
|
490
576
|
};
|
|
491
577
|
const settings: XgmmlSettings = { labelAliases: false, cytoscapeEscapes: false, zAs: prepared.zAs };
|
|
492
578
|
new XgmmlEmitter(doc, dialect, sink, report, prepared.inner, settings, extras).emit(graph);
|
|
493
579
|
if (groups.length > 0) {
|
|
580
|
+
const missing = groups.reduce((n, g) => n + g.members.length, 0);
|
|
494
581
|
report.warning(
|
|
495
582
|
"unsupported",
|
|
496
583
|
CYS_ISSUE.COLLAPSED_GROUP,
|
|
497
|
-
`${groups.length} collapsed group(
|
|
584
|
+
`${groups.length} collapsed group${plural(groups.length)} ${agree(groups.length, "holds", "hold")} ${missing} member${plural(missing)} that are not in the network; they are listed in meta.extra.cytoscape.groups`,
|
|
498
585
|
);
|
|
499
586
|
}
|
|
500
587
|
reportRootOnly(parsed, report, choice.network.name);
|
|
501
|
-
writeViewPositions(
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
588
|
+
writeViewPositions(sink, extraViews, extraIds, report, prepared.inner, restoredIds);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* The ids of the nodes Cytoscape marks as groups.
|
|
593
|
+
* @param nodes - the network's node records
|
|
594
|
+
* @returns their ids
|
|
595
|
+
*/
|
|
596
|
+
function groupNodeIds(nodes: readonly NodeRec[]): Set<string> {
|
|
597
|
+
return new Set(
|
|
598
|
+
nodes
|
|
599
|
+
.filter((n) => n.atts.some((a) => a.name === "__isGroup" && isCyTrue(a.value)))
|
|
600
|
+
.flatMap((n) => (n.id === null ? [] : [n.id])),
|
|
507
601
|
);
|
|
508
602
|
}
|
|
509
603
|
|
|
604
|
+
/**
|
|
605
|
+
* Record a collapsed group's member that is not in the network.
|
|
606
|
+
* @param groups - the groups with missing members, added to
|
|
607
|
+
* @param group - the group's id
|
|
608
|
+
* @param member - the member's id
|
|
609
|
+
*/
|
|
610
|
+
function addMissingMember(groups: { group: string; members: string[] }[], group: string, member: string): void {
|
|
611
|
+
const record = groups.find((g) => g.group === group);
|
|
612
|
+
if (record === undefined) {
|
|
613
|
+
groups.push({ group, members: [member] });
|
|
614
|
+
} else if (!record.members.includes(member)) {
|
|
615
|
+
record.members.push(member);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** What reading one subnetwork's tables shares. */
|
|
620
|
+
interface TableContext {
|
|
621
|
+
readonly choice: Choice3;
|
|
622
|
+
readonly graph: Choice3["graph"];
|
|
623
|
+
readonly nodes: ReadonlyMap<string, NodeRec>;
|
|
624
|
+
readonly edges: ReadonlyMap<string, EdgeRec>;
|
|
625
|
+
/** The ids of the elements the network file declares, inside this network or outside it. */
|
|
626
|
+
readonly declared: ReadonlySet<string>;
|
|
627
|
+
readonly virtuals: readonly VirtualColumn[];
|
|
628
|
+
readonly tableOf: (path: string) => Promise<CyTable | null>;
|
|
629
|
+
readonly report: ImportReportBuilder;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Add the cells of a subnetwork's tables to its node, edge and network records.
|
|
634
|
+
* @param prepared - the prepared import
|
|
635
|
+
* @param choice - the subnetwork
|
|
636
|
+
* @param doc - its network file
|
|
637
|
+
* @param nodes - its node records by id
|
|
638
|
+
* @param edges - its edge records by id
|
|
639
|
+
* @returns the number of rows that name no element of the network
|
|
640
|
+
*/
|
|
641
|
+
async function applyTables(
|
|
642
|
+
prepared: Prepared,
|
|
643
|
+
choice: Choice3,
|
|
644
|
+
doc: Parsed["doc"],
|
|
645
|
+
nodes: ReadonlyMap<string, NodeRec>,
|
|
646
|
+
edges: ReadonlyMap<string, EdgeRec>,
|
|
647
|
+
): Promise<number> {
|
|
648
|
+
const { report, inner, session } = prepared;
|
|
649
|
+
const { layout } = session;
|
|
650
|
+
// each network reads its tables into its own report, so a shared table's issues, and a
|
|
651
|
+
// table that fails the import, land in every network that reads it.
|
|
652
|
+
// ponytail: a table shared by several networks is inflated and parsed once per network (the
|
|
653
|
+
// byte budget is charged once); cache the parse and relay its issues if importAll gets slow
|
|
654
|
+
const tables = new Map<string, Promise<CyTable | null>>();
|
|
655
|
+
const tableOf = (path: string): Promise<CyTable | null> => {
|
|
656
|
+
let pending = tables.get(path);
|
|
657
|
+
if (pending === undefined) {
|
|
658
|
+
const entry = layout.tables.find((t) => t.tablePath === path);
|
|
659
|
+
pending =
|
|
660
|
+
entry === undefined
|
|
661
|
+
? Promise.resolve(null)
|
|
662
|
+
: session.read(entry, report).then((bytes) => readCyTable(bytes, path, entry.name, report, inner));
|
|
663
|
+
tables.set(path, pending);
|
|
664
|
+
}
|
|
665
|
+
return pending;
|
|
666
|
+
};
|
|
667
|
+
const ctx: TableContext = {
|
|
668
|
+
choice,
|
|
669
|
+
graph: choice.graph,
|
|
670
|
+
nodes,
|
|
671
|
+
edges,
|
|
672
|
+
// a row of an element the file declares outside this network (a collapsed group's member, a
|
|
673
|
+
// meta-edge) is Cytoscape's bookkeeping, not a stale row
|
|
674
|
+
declared: new Set<string>([
|
|
675
|
+
...doc.nodes.flatMap((n) => (n.id === null ? [] : [n.id])),
|
|
676
|
+
...doc.edges.flatMap((e) => (e.id === null ? [] : [e.id])),
|
|
677
|
+
]),
|
|
678
|
+
virtuals: layout.cytables === null ? [] : await readVirtuals(session, layout.cytables, report, inner),
|
|
679
|
+
tableOf,
|
|
680
|
+
report,
|
|
681
|
+
};
|
|
682
|
+
let unmatched = 0;
|
|
683
|
+
for (const entry of layout.tables.filter((t) => t.network === choice.graphId && t.element !== null)) {
|
|
684
|
+
throwIfAborted(inner.signal);
|
|
685
|
+
const table = await tableOf(entry.tablePath);
|
|
686
|
+
if (table !== null) {
|
|
687
|
+
unmatched += await applyTable(ctx, entry, table);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return unmatched;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Add the cells of one table, and of the shared columns that extend it, to the records.
|
|
695
|
+
* @param ctx - the shared state
|
|
696
|
+
* @param entry - the table's entry
|
|
697
|
+
* @param table - the table
|
|
698
|
+
* @returns the number of rows that name no element of the network
|
|
699
|
+
*/
|
|
700
|
+
async function applyTable(ctx: TableContext, entry: TableEntry, table: CyTable): Promise<number> {
|
|
701
|
+
const local = entry.namespace === "LOCAL_ATTRS";
|
|
702
|
+
const namespace = local ? null : entry.namespace;
|
|
703
|
+
const hidden = !local && entry.namespace !== "SHARED_ATTRS";
|
|
704
|
+
const target = (key: string): AttRec[] | undefined => {
|
|
705
|
+
if (entry.element === "network") {
|
|
706
|
+
return key === ctx.choice.graphId ? ctx.graph.atts : undefined;
|
|
707
|
+
}
|
|
708
|
+
return (entry.element === "node" ? ctx.nodes : ctx.edges).get(key)?.atts;
|
|
709
|
+
};
|
|
710
|
+
let unmatched = 0;
|
|
711
|
+
for (const [key, cells] of table.rows) {
|
|
712
|
+
const atts = target(key);
|
|
713
|
+
if (atts !== undefined) {
|
|
714
|
+
atts.push(...rowAtts(table, key, cells, namespace, hidden));
|
|
715
|
+
} else if (!ctx.declared.has(key)) {
|
|
716
|
+
unmatched++;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
for (const virtual of await virtualColumnsOf(table, ctx.virtuals, ctx.tableOf, ctx.report)) {
|
|
720
|
+
// a shared column named like a local one is renamed; the local one keeps the name
|
|
721
|
+
const owned = table.columns.some((c) => c.name === virtual.column.name);
|
|
722
|
+
const ns = local ? sharedNamespace(owned) : namespace;
|
|
723
|
+
for (const [key, text] of virtual.values) {
|
|
724
|
+
const atts = target(key);
|
|
725
|
+
if (atts !== undefined) {
|
|
726
|
+
pushAtt(atts, cellAtt(virtual.column, text, table.lines.get(key) ?? 0, ns, hidden));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return unmatched;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* The attributes of one table row's cells (the first cell is the SUID key).
|
|
735
|
+
* @param table - the table
|
|
736
|
+
* @param key - the row's key
|
|
737
|
+
* @param cells - the row's cells
|
|
738
|
+
* @param namespace - the namespace the attributes are written in, or null
|
|
739
|
+
* @param hidden - whether the table is one of Cytoscape's hidden ones
|
|
740
|
+
* @returns the attributes of the cells that hold a value
|
|
741
|
+
*/
|
|
742
|
+
function rowAtts(
|
|
743
|
+
table: CyTable,
|
|
744
|
+
key: string,
|
|
745
|
+
cells: readonly string[],
|
|
746
|
+
namespace: string | null,
|
|
747
|
+
hidden: boolean,
|
|
748
|
+
): AttRec[] {
|
|
749
|
+
const line = table.lines.get(key) ?? 0;
|
|
750
|
+
const out: AttRec[] = [];
|
|
751
|
+
for (let i = 1; i < table.columns.length && i < cells.length; i++) {
|
|
752
|
+
pushAtt(out, cellAtt(table.columns[i], cells[i], line, namespace, hidden));
|
|
753
|
+
}
|
|
754
|
+
return out;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* The namespace of a shared column in a local table: renamed when a local column has its name.
|
|
759
|
+
* @param owned - whether a local column has the shared column's name
|
|
760
|
+
* @returns "SHARED_ATTRS", or null for none
|
|
761
|
+
*/
|
|
762
|
+
function sharedNamespace(owned: boolean): string | null {
|
|
763
|
+
return owned ? "SHARED_ATTRS" : null;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Add an attribute to a record's list, unless there is none.
|
|
768
|
+
* @param atts - the record's attributes
|
|
769
|
+
* @param att - the attribute, or null
|
|
770
|
+
*/
|
|
771
|
+
function pushAtt(atts: AttRec[], att: AttRec | null): void {
|
|
772
|
+
if (att !== null) {
|
|
773
|
+
atts.push(att);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Read a subnetwork's views: the first (the lowest view SUID, whatever the order of the entries)
|
|
779
|
+
* fills the records' coordinates and graphics, each further one gives positions only. A view that
|
|
780
|
+
* cannot be read is recorded and skipped.
|
|
781
|
+
* @param prepared - the prepared import
|
|
782
|
+
* @param choice - the subnetwork
|
|
783
|
+
* @param nodes - its node records by id
|
|
784
|
+
* @param edges - its edge records by id
|
|
785
|
+
* @returns the first view's style, and the positions and SUIDs of the further views
|
|
786
|
+
*/
|
|
787
|
+
async function readViews(
|
|
788
|
+
prepared: Prepared,
|
|
789
|
+
choice: Choice3,
|
|
790
|
+
nodes: ReadonlyMap<string, NodeRec>,
|
|
791
|
+
edges: ReadonlyMap<string, EdgeRec>,
|
|
792
|
+
): Promise<{
|
|
793
|
+
visualStyle: string | null;
|
|
794
|
+
extraViews: Map<string, [number, number, number]>[];
|
|
795
|
+
extraIds: string[];
|
|
796
|
+
}> {
|
|
797
|
+
const { report, inner, session } = prepared;
|
|
798
|
+
const views = session.layout.views
|
|
799
|
+
.filter((v) => v.network === choice.graphId)
|
|
800
|
+
.sort((a, b) => a.view.length - b.view.length || (a.view < b.view ? -1 : Number(a.view > b.view)));
|
|
801
|
+
let visualStyle: string | null = null;
|
|
802
|
+
const extraViews: Map<string, [number, number, number]>[] = [];
|
|
803
|
+
const extraIds: string[] = [];
|
|
804
|
+
let first = true;
|
|
805
|
+
for (const view of views) {
|
|
806
|
+
const viewDoc = (await parseOptionalEntry(session, view, report, inner, true))?.doc;
|
|
807
|
+
if (viewDoc === undefined) {
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
if (first) {
|
|
811
|
+
first = false;
|
|
812
|
+
visualStyle = viewDoc.root.attrs.get("cy:visualStyle") ?? null;
|
|
813
|
+
applyView(viewDoc, choice.graph, nodes, edges, report, view.name);
|
|
814
|
+
} else {
|
|
815
|
+
extraViews.push(viewPositions(viewDoc, prepared.zAs, nodes, report, view.name));
|
|
816
|
+
extraIds.push(view.view);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return { visualStyle, extraViews, extraIds };
|
|
820
|
+
}
|
|
821
|
+
|
|
510
822
|
/**
|
|
511
823
|
* The virtual columns of `tables/cytables.xml`.
|
|
512
824
|
* @param session - the session
|
|
@@ -521,9 +833,28 @@ async function readVirtuals(
|
|
|
521
833
|
report: ImportReportBuilder,
|
|
522
834
|
inner: ResolvedImportOptions,
|
|
523
835
|
): Promise<VirtualColumn[]> {
|
|
524
|
-
const tree = await
|
|
836
|
+
const tree = await parseXmlTree(await session.read(entry, report), entry.name, report, inner);
|
|
837
|
+
if (typeof tree === "string") {
|
|
838
|
+
// only the shared columns are lost: every table's own columns are still read
|
|
839
|
+
report.error(
|
|
840
|
+
"parse-error",
|
|
841
|
+
CYS_ISSUE.TABLE,
|
|
842
|
+
`${entryLabel(entry.name)}: ${tree}; its virtual columns are not read`,
|
|
843
|
+
);
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
525
846
|
const out: VirtualColumn[] = [];
|
|
526
847
|
for (const node of elementsNamed(tree, "virtualColumn")) {
|
|
848
|
+
const missing = ["name", "targetTable", "sourceTable", "sourceColumn"].filter((k) => !node.attrs.has(k));
|
|
849
|
+
if (missing.length > 0) {
|
|
850
|
+
const named = node.attrs.has("name") ? ` "${node.attrs.get("name") ?? ""}"` : "";
|
|
851
|
+
report.error(
|
|
852
|
+
"parse-error",
|
|
853
|
+
CYS_ISSUE.TABLE,
|
|
854
|
+
`${entryLabel(entry.name)}: a virtual column${named} has no ${missing.join(", ")}; the column is not read`,
|
|
855
|
+
);
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
527
858
|
const a = (key: string): string => node.attrs.get(key) ?? "";
|
|
528
859
|
out.push({
|
|
529
860
|
name: a("name"),
|
|
@@ -572,12 +903,14 @@ function applyView(
|
|
|
572
903
|
entry: string,
|
|
573
904
|
): void {
|
|
574
905
|
let dangling = 0;
|
|
906
|
+
const seen = new Set<string>();
|
|
575
907
|
for (const node of view.nodes) {
|
|
576
908
|
const target = node.viewId === null ? undefined : nodes.get(node.viewId);
|
|
577
909
|
if (target === undefined) {
|
|
578
910
|
dangling++;
|
|
579
911
|
continue;
|
|
580
912
|
}
|
|
913
|
+
duplicateViewNode(seen, node, report, entry);
|
|
581
914
|
target.x = node.x;
|
|
582
915
|
target.y = node.y;
|
|
583
916
|
target.z = node.z;
|
|
@@ -602,28 +935,93 @@ function applyView(
|
|
|
602
935
|
report.warning(
|
|
603
936
|
"validation-error",
|
|
604
937
|
CYS_ISSUE.DANGLING_REFERENCE,
|
|
605
|
-
`${entry}: ${dangling} view element(
|
|
938
|
+
`${entryLabel(entry)}: ${dangling} view element${plural(dangling)} ${agree(dangling, "names", "name")} no node or edge of the network; they are not read`,
|
|
606
939
|
);
|
|
607
940
|
}
|
|
608
941
|
}
|
|
609
942
|
|
|
610
943
|
/**
|
|
611
|
-
*
|
|
612
|
-
*
|
|
944
|
+
* W_DUPLICATE_NODE for a view element that repeats a node an earlier one of the view gave.
|
|
945
|
+
* @param seen - the model node ids seen so far in the view
|
|
946
|
+
* @param node - the view element
|
|
947
|
+
* @param report - the report
|
|
948
|
+
* @param entry - the view entry name
|
|
949
|
+
*/
|
|
950
|
+
function duplicateViewNode(seen: Set<string>, node: NodeRec, report: ImportReportBuilder, entry: string): void {
|
|
951
|
+
const id = node.viewId as string;
|
|
952
|
+
if (seen.has(id)) {
|
|
953
|
+
report.warning(
|
|
954
|
+
"validation-error",
|
|
955
|
+
XGMML_ISSUE.DUPLICATE_NODE,
|
|
956
|
+
`${entryLabel(entry)}: the view gives node "${id}" twice; the later element is used`,
|
|
957
|
+
{ line: node.line, element: id },
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
seen.add(id);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* The positions of a further view, by model node id, read by the first view's rules: y flipped
|
|
965
|
+
* to y-up, z in the position only under zAs "position", a coordinate that does not parse
|
|
966
|
+
* E_BAD_VALUE (the position is unset; a bad z is 0), an element naming no node of the network
|
|
967
|
+
* counted in one W_DANGLING_REFERENCE.
|
|
613
968
|
* @param view - the view document
|
|
614
969
|
* @param zAs - where z goes
|
|
970
|
+
* @param nodes - the network's node records by id
|
|
971
|
+
* @param report - the report
|
|
972
|
+
* @param entry - the view entry name
|
|
615
973
|
* @returns the positions
|
|
616
974
|
*/
|
|
617
|
-
function viewPositions(
|
|
975
|
+
function viewPositions(
|
|
976
|
+
view: XgmmlDocument,
|
|
977
|
+
zAs: "column" | "position",
|
|
978
|
+
nodes: ReadonlyMap<string, NodeRec>,
|
|
979
|
+
report: ImportReportBuilder,
|
|
980
|
+
entry: string,
|
|
981
|
+
): Map<string, [number, number, number]> {
|
|
618
982
|
const out = new Map<string, [number, number, number]>();
|
|
983
|
+
const seen = new Set<string>();
|
|
984
|
+
let dangling = 0;
|
|
985
|
+
const coordinate = (text: string | null, node: NodeRec): number | null => {
|
|
986
|
+
if (text === null) {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
const n = parseCoordinate(text);
|
|
990
|
+
if (n === null) {
|
|
991
|
+
report.error(
|
|
992
|
+
"validation-error",
|
|
993
|
+
XGMML_ISSUE.BAD_VALUE,
|
|
994
|
+
`${entryLabel(entry)}: graphics coordinate "${text}" is not a number`,
|
|
995
|
+
{
|
|
996
|
+
line: node.line,
|
|
997
|
+
element: node.viewId,
|
|
998
|
+
},
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
return n;
|
|
1002
|
+
};
|
|
619
1003
|
for (const node of view.nodes) {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
1004
|
+
if (node.viewId === null || !nodes.has(node.viewId)) {
|
|
1005
|
+
dangling++;
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
duplicateViewNode(seen, node, report, entry);
|
|
1009
|
+
const x = coordinate(node.x, node);
|
|
1010
|
+
const y = coordinate(node.y, node);
|
|
1011
|
+
const z = zAs === "position" ? coordinate(node.z, node) : null;
|
|
1012
|
+
if (x !== null && y !== null) {
|
|
1013
|
+
out.set(node.viewId, [x, y === 0 ? 0 : -y, z ?? 0]);
|
|
1014
|
+
} else {
|
|
1015
|
+
out.delete(node.viewId);
|
|
625
1016
|
}
|
|
626
1017
|
}
|
|
1018
|
+
if (dangling > 0) {
|
|
1019
|
+
report.warning(
|
|
1020
|
+
"validation-error",
|
|
1021
|
+
CYS_ISSUE.DANGLING_REFERENCE,
|
|
1022
|
+
`${entryLabel(entry)}: ${dangling} view element${plural(dangling)} ${agree(dangling, "names", "name")} no node of the network; they are not read`,
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
627
1025
|
return out;
|
|
628
1026
|
}
|
|
629
1027
|
|
|
@@ -634,6 +1032,7 @@ function viewPositions(view: XgmmlDocument, zAs: "column" | "position"): Map<str
|
|
|
634
1032
|
* @param viewIds - each view's SUID
|
|
635
1033
|
* @param report - the report
|
|
636
1034
|
* @param common - the id rule
|
|
1035
|
+
* @param restoredIds - the original ids of mangled nodes, by written id
|
|
637
1036
|
*/
|
|
638
1037
|
function writeViewPositions(
|
|
639
1038
|
sink: GraphSink,
|
|
@@ -641,6 +1040,7 @@ function writeViewPositions(
|
|
|
641
1040
|
viewIds: readonly string[],
|
|
642
1041
|
report: ImportReportBuilder,
|
|
643
1042
|
common: ResolvedImportOptions,
|
|
1043
|
+
restoredIds: ReadonlyMap<string, string>,
|
|
644
1044
|
): void {
|
|
645
1045
|
const coercer = new IdCoercer(common.ids);
|
|
646
1046
|
views.forEach((positions, i) => {
|
|
@@ -668,7 +1068,7 @@ function writeViewPositions(
|
|
|
668
1068
|
for (const [id, xyz] of positions) {
|
|
669
1069
|
let index: number;
|
|
670
1070
|
try {
|
|
671
|
-
index = sink.indexOf(coercer.text(id));
|
|
1071
|
+
index = sink.indexOf(coercer.text(restoredIds.get(id) ?? id));
|
|
672
1072
|
} catch {
|
|
673
1073
|
continue; // an id the id rule refuses was refused, and recorded, as a node already
|
|
674
1074
|
}
|
|
@@ -679,6 +1079,30 @@ function writeViewPositions(
|
|
|
679
1079
|
});
|
|
680
1080
|
}
|
|
681
1081
|
|
|
1082
|
+
/**
|
|
1083
|
+
* The original ids an exporter's `sanitizeIds: "mangle"` kept in the node table's
|
|
1084
|
+
* `graphty:originalId` column; each such cell is taken out of the node's columns.
|
|
1085
|
+
* @param nodes - the node records, table cells merged in
|
|
1086
|
+
* @returns the original id by written id
|
|
1087
|
+
*/
|
|
1088
|
+
function restoreIds(nodes: readonly NodeRec[]): Map<string, string> {
|
|
1089
|
+
const out = new Map<string, string>();
|
|
1090
|
+
for (const node of nodes) {
|
|
1091
|
+
const at = node.atts.findIndex(
|
|
1092
|
+
(a) =>
|
|
1093
|
+
a.name === ORIGINAL_ID_ATTRIBUTE &&
|
|
1094
|
+
(a.namespace ?? null) === null &&
|
|
1095
|
+
a.value !== null &&
|
|
1096
|
+
a.children.length === 0,
|
|
1097
|
+
);
|
|
1098
|
+
if (node.id !== null && at >= 0) {
|
|
1099
|
+
out.set(node.id, node.atts[at].value as string);
|
|
1100
|
+
node.atts.splice(at, 1);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
return out;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
682
1106
|
/**
|
|
683
1107
|
* The network's name from its own table (the LOCAL `name` att), else null.
|
|
684
1108
|
* @param graph - the subnetwork with its table atts
|
|
@@ -754,7 +1178,7 @@ function reportRootOnly(parsed: Parsed, report: ImportReportBuilder, entry: stri
|
|
|
754
1178
|
report.warning(
|
|
755
1179
|
"unsupported",
|
|
756
1180
|
XGMML_ISSUE.ROOT_ONLY_ELEMENTS,
|
|
757
|
-
`${entry}: ${nodes}
|
|
1181
|
+
`${entryLabel(entry)}: ${elementCount(nodes, edges)} ${agree(nodes + edges, "belongs", "belong")} to none of the session's networks (group meta-edges or collapsed group members) and ${agree(nodes + edges, "was", "were")} not read`,
|
|
758
1182
|
);
|
|
759
1183
|
}
|
|
760
1184
|
}
|
|
@@ -778,8 +1202,15 @@ async function networks2(
|
|
|
778
1202
|
if (layout.cysession === null) {
|
|
779
1203
|
return report.fail(CYS_ISSUE.NOT_SESSION, "the session has no cysession.xml");
|
|
780
1204
|
}
|
|
781
|
-
const tree = await readXmlTree(await session.read(layout.cysession), layout.cysession.name, report, inner);
|
|
1205
|
+
const tree = await readXmlTree(await session.read(layout.cysession, report), layout.cysession.name, report, inner);
|
|
782
1206
|
const documentVersion = tree.attrs.get("documentVersion") ?? "";
|
|
1207
|
+
if (documentVersion.length > 0 && !/^\d+(\.\d+)*$/.test(documentVersion.trim())) {
|
|
1208
|
+
report.warning(
|
|
1209
|
+
"validation-error",
|
|
1210
|
+
XGMML_ISSUE.DOCUMENT_VERSION,
|
|
1211
|
+
`${layout.cysession.name}: documentVersion "${documentVersion}" does not parse; the session is read as Cytoscape 2.x`,
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
783
1214
|
if (documentVersion.startsWith("3")) {
|
|
784
1215
|
report.fail(
|
|
785
1216
|
CYS_ISSUE.VERSION,
|
|
@@ -796,16 +1227,40 @@ async function networks2(
|
|
|
796
1227
|
const missing: string[] = [];
|
|
797
1228
|
const named = new Set<SessionEntry>();
|
|
798
1229
|
for (const record of elementsNamed(tree, "network")) {
|
|
799
|
-
const
|
|
800
|
-
const filename = record.attrs.get("filename") ?? `${
|
|
801
|
-
if (
|
|
1230
|
+
const given = record.attrs.get("id");
|
|
1231
|
+
const filename = record.attrs.get("filename") ?? (given === undefined ? undefined : `${given}.xgmml`);
|
|
1232
|
+
if (given === "Network Root") {
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
if (filename === undefined) {
|
|
1236
|
+
sessionRecord(
|
|
1237
|
+
report,
|
|
1238
|
+
layout.cysession.name,
|
|
1239
|
+
"a <network> has neither an id nor a filename; it is not read",
|
|
1240
|
+
);
|
|
802
1241
|
continue;
|
|
803
1242
|
}
|
|
1243
|
+
const id = given ?? urlDecode(filename.replace(/\.xgmml$/i, ""));
|
|
1244
|
+
if (given === undefined) {
|
|
1245
|
+
sessionRecord(
|
|
1246
|
+
report,
|
|
1247
|
+
layout.cysession.name,
|
|
1248
|
+
`a <network> for ${filename} has no id; it is named "${id}" after its file`,
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
804
1251
|
const file = byDecoded.get(filename);
|
|
805
1252
|
if (file === undefined) {
|
|
806
1253
|
missing.push(filename);
|
|
807
1254
|
continue;
|
|
808
1255
|
}
|
|
1256
|
+
if (named.has(file)) {
|
|
1257
|
+
sessionRecord(
|
|
1258
|
+
report,
|
|
1259
|
+
layout.cysession.name,
|
|
1260
|
+
`the <network> "${id}" names ${filename}, which an earlier record already names; it is not read a second time`,
|
|
1261
|
+
);
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
809
1264
|
named.add(file);
|
|
810
1265
|
const parent = record.children.find((c) => c.name === "parent")?.attrs.get("id") ?? null;
|
|
811
1266
|
choices.push({
|
|
@@ -820,7 +1275,7 @@ async function networks2(
|
|
|
820
1275
|
report.warning(
|
|
821
1276
|
"validation-error",
|
|
822
1277
|
CYS_ISSUE.DANGLING_REFERENCE,
|
|
823
|
-
`cysession.xml names ${missing.length} network file(
|
|
1278
|
+
`cysession.xml names ${missing.length} network file${plural(missing.length)} the session does not hold: ${listed(missing)}`,
|
|
824
1279
|
);
|
|
825
1280
|
}
|
|
826
1281
|
const unnamed = order.filter((e) => !named.has(e)).map((e) => e.name);
|
|
@@ -828,12 +1283,23 @@ async function networks2(
|
|
|
828
1283
|
report.warning(
|
|
829
1284
|
"unsupported",
|
|
830
1285
|
CYS_ISSUE.ENTRY_SKIPPED,
|
|
831
|
-
`${unnamed.length} network file(
|
|
1286
|
+
`${unnamed.length} network file${plural(unnamed.length)} ${agree(unnamed.length, "is", "are")} not in cysession.xml's network tree and were not read: ${listed(unnamed)}`,
|
|
832
1287
|
);
|
|
833
1288
|
}
|
|
834
1289
|
return choices.sort((a, b) => order.indexOf(a.file) - order.indexOf(b.file));
|
|
835
1290
|
}
|
|
836
1291
|
|
|
1292
|
+
/**
|
|
1293
|
+
* W_CYS_SESSION_RECORD: a cysession.xml network record without an id, or one naming a file an
|
|
1294
|
+
* earlier record names.
|
|
1295
|
+
* @param report - the report
|
|
1296
|
+
* @param entry - the cysession.xml entry name
|
|
1297
|
+
* @param message - what is wrong and what is done
|
|
1298
|
+
*/
|
|
1299
|
+
function sessionRecord(report: ImportReportBuilder, entry: string, message: string): void {
|
|
1300
|
+
report.warning("validation-error", CYS_ISSUE.SESSION_RECORD, `${entryLabel(entry)}: ${message}`);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
837
1303
|
/**
|
|
838
1304
|
* Import one network of a 2.x session.
|
|
839
1305
|
* @param prepared - the prepared import
|
|
@@ -843,17 +1309,25 @@ async function networks2(
|
|
|
843
1309
|
async function import2(prepared: Prepared, choice: Choice2, sink: GraphSink): Promise<void> {
|
|
844
1310
|
const { report, inner, session } = prepared;
|
|
845
1311
|
const { doc, dialect } = await parseEntry(session, choice.file, report, inner);
|
|
1312
|
+
let unmatched = 0;
|
|
846
1313
|
for (const [list, column] of [
|
|
847
1314
|
["selectedNodes", SELECTED_COLUMN],
|
|
848
1315
|
["hiddenNodes", HIDDEN_COLUMN],
|
|
849
1316
|
] as const) {
|
|
850
|
-
mark(doc.nodes, idsIn(choice.record, list), column);
|
|
1317
|
+
unmatched += mark(doc.nodes, idsIn(choice.record, list), column);
|
|
851
1318
|
}
|
|
852
1319
|
for (const [list, column] of [
|
|
853
1320
|
["selectedEdges", SELECTED_COLUMN],
|
|
854
1321
|
["hiddenEdges", HIDDEN_COLUMN],
|
|
855
1322
|
] as const) {
|
|
856
|
-
mark(doc.edges, idsIn(choice.record, list), column);
|
|
1323
|
+
unmatched += mark(doc.edges, idsIn(choice.record, list), column);
|
|
1324
|
+
}
|
|
1325
|
+
if (unmatched > 0) {
|
|
1326
|
+
report.warning(
|
|
1327
|
+
"validation-error",
|
|
1328
|
+
CYS_ISSUE.DANGLING_REFERENCE,
|
|
1329
|
+
`cysession.xml lists ${unmatched} selected or hidden name${plural(unmatched)} that ${choice.file.name} does not hold; they are not read`,
|
|
1330
|
+
);
|
|
857
1331
|
}
|
|
858
1332
|
const cyMeta = sessionMeta(prepared, {
|
|
859
1333
|
collection: null,
|
|
@@ -896,12 +1370,20 @@ function idsIn(record: XmlNode, list: string): Set<string> {
|
|
|
896
1370
|
* @param records - the node or edge records
|
|
897
1371
|
* @param ids - the listed names
|
|
898
1372
|
* @param column - the column
|
|
1373
|
+
* @returns how many listed names match no record
|
|
899
1374
|
*/
|
|
900
|
-
function mark(records: readonly (NodeRec | EdgeRec)[], ids: ReadonlySet<string>, column: string):
|
|
1375
|
+
function mark(records: readonly (NodeRec | EdgeRec)[], ids: ReadonlySet<string>, column: string): number {
|
|
901
1376
|
if (ids.size === 0) {
|
|
902
|
-
return;
|
|
1377
|
+
return 0;
|
|
903
1378
|
}
|
|
1379
|
+
const matched = new Set<string>();
|
|
904
1380
|
for (const record of records) {
|
|
1381
|
+
if (record.id !== null && ids.has(record.id)) {
|
|
1382
|
+
matched.add(record.id);
|
|
1383
|
+
}
|
|
1384
|
+
if (record.label !== null && ids.has(record.label)) {
|
|
1385
|
+
matched.add(record.label);
|
|
1386
|
+
}
|
|
905
1387
|
if ((record.id !== null && ids.has(record.id)) || (record.label !== null && ids.has(record.label))) {
|
|
906
1388
|
record.atts.push({
|
|
907
1389
|
name: column,
|
|
@@ -921,6 +1403,7 @@ function mark(records: readonly (NodeRec | EdgeRec)[], ids: ReadonlySet<string>,
|
|
|
921
1403
|
});
|
|
922
1404
|
}
|
|
923
1405
|
}
|
|
1406
|
+
return ids.size - matched.size;
|
|
924
1407
|
}
|
|
925
1408
|
|
|
926
1409
|
// ---------------------------------------------------------------------------------------- shared
|
|
@@ -957,14 +1440,14 @@ function sessionMeta(
|
|
|
957
1440
|
report.warning(
|
|
958
1441
|
"unsupported",
|
|
959
1442
|
CYS_ISSUE.STYLES_NOT_IMPORTED,
|
|
960
|
-
`the session's styles (${layout.styles.map((s) => s.path).join(", ")}) are not applied
|
|
1443
|
+
`the session's styles (${layout.styles.map((s) => s.path).join(", ")}) are not applied`,
|
|
961
1444
|
);
|
|
962
1445
|
}
|
|
963
1446
|
if (layout.skipped.length > 0) {
|
|
964
1447
|
report.warning(
|
|
965
1448
|
"unsupported",
|
|
966
1449
|
CYS_ISSUE.ENTRY_SKIPPED,
|
|
967
|
-
`${layout.skipped.length} entries hold no graph data and were not read (apps, global tables, properties, images): ${listed(layout.skipped)}`,
|
|
1450
|
+
`${layout.skipped.length} ${layout.skipped.length === 1 ? "entry holds" : "entries hold"} no graph data and ${agree(layout.skipped.length, "was", "were")} not read (apps, global tables, properties, images): ${listed(layout.skipped)}`,
|
|
968
1451
|
);
|
|
969
1452
|
}
|
|
970
1453
|
return {
|
|
@@ -1011,14 +1494,16 @@ async function importCys(
|
|
|
1011
1494
|
options,
|
|
1012
1495
|
prepared.report,
|
|
1013
1496
|
);
|
|
1014
|
-
if (prepared.choices.length > 1) {
|
|
1497
|
+
if (prepared.choices.length > 1 && !graphChosen(options)) {
|
|
1015
1498
|
prepared.report.warning(
|
|
1016
1499
|
"unsupported",
|
|
1017
1500
|
CYS_ISSUE.MULTIPLE_GRAPHS,
|
|
1018
|
-
`the session holds ${prepared.choices.length} networks;
|
|
1501
|
+
`the session holds ${prepared.choices.length} networks; only the first is read (importAllGraphs() reads every one; graphIndex or graphName chooses one)`,
|
|
1019
1502
|
);
|
|
1020
1503
|
}
|
|
1021
|
-
|
|
1504
|
+
const report = await importChoice(prepared, index, sink);
|
|
1505
|
+
prepared.session.done();
|
|
1506
|
+
return report;
|
|
1022
1507
|
}
|
|
1023
1508
|
|
|
1024
1509
|
/**
|
|
@@ -1033,17 +1518,19 @@ async function importAllCys(
|
|
|
1033
1518
|
sinkFor: (index: number) => GraphSink,
|
|
1034
1519
|
options?: CysImportOptions & CommonImportOptions,
|
|
1035
1520
|
): Promise<ImportReport[]> {
|
|
1036
|
-
|
|
1037
|
-
|
|
1521
|
+
// the archive is opened and its network files parsed once: one byte budget, one progress,
|
|
1522
|
+
// and every network's report starts from what reading the session recorded
|
|
1523
|
+
const first = await prepare(input, null, options);
|
|
1524
|
+
const opened = first.report.fork();
|
|
1038
1525
|
const reports: ImportReport[] = [];
|
|
1039
1526
|
for (let i = 0; i < first.choices.length; i++) {
|
|
1040
1527
|
const sink = sinkFor(i);
|
|
1041
|
-
|
|
1042
|
-
const prepared = i === 0 ? first : await prepare(bytes, null, options);
|
|
1528
|
+
const prepared = i === 0 ? first : { ...first, report: opened.fork() };
|
|
1043
1529
|
reportSinkOptions(sink, options, prepared.report, true);
|
|
1044
1530
|
reportUnusedOptions(options, prepared.report, USED_OPTIONS);
|
|
1045
1531
|
reports.push(await importChoice(prepared, i, sink));
|
|
1046
1532
|
}
|
|
1533
|
+
first.session.done();
|
|
1047
1534
|
return reports;
|
|
1048
1535
|
}
|
|
1049
1536
|
|
|
@@ -1068,8 +1555,18 @@ async function listCysGraphs(
|
|
|
1068
1555
|
);
|
|
1069
1556
|
}
|
|
1070
1557
|
|
|
1071
|
-
/**
|
|
1072
|
-
|
|
1558
|
+
/**
|
|
1559
|
+
* A session's marker or folder name in the head of the archive, or an entry only a session has
|
|
1560
|
+
* (Cytoscape's app state, a network, view or table file), for a session whose folder was renamed.
|
|
1561
|
+
* The head is decoded whole, so a name can sit anywhere in it.
|
|
1562
|
+
*/
|
|
1563
|
+
const SESSION_NAMES: readonly RegExp[] = [
|
|
1564
|
+
/CytoscapeSession|cysession\.xml/,
|
|
1565
|
+
/(?<!\d)\d+\.\d+\.\d+\.version/,
|
|
1566
|
+
/(?:^|\/)apps\/org\.cytoscape\./,
|
|
1567
|
+
/(?:^|\/)(?:networks|views)\/\d+-[^/]*\.xgmml/,
|
|
1568
|
+
/(?:^|\/)tables\/\d+-[^/]*\/[^/]+\.cytable/,
|
|
1569
|
+
];
|
|
1073
1570
|
|
|
1074
1571
|
/**
|
|
1075
1572
|
* Confidence that a head of bytes is a Cytoscape session: 0.95 for a zip whose head names the
|
|
@@ -1078,19 +1575,24 @@ const SESSION_NAME = /CytoscapeSession|cysession\.xml|\d+\.\d+\.\d+\.version/;
|
|
|
1078
1575
|
* start after a short stub (a self-extracting archive) within the head.
|
|
1079
1576
|
* @param head - the first bytes
|
|
1080
1577
|
* @returns the confidence
|
|
1578
|
+
* @category Plugin helpers
|
|
1081
1579
|
*/
|
|
1082
1580
|
export function sniffCys(head: Uint8Array): number {
|
|
1083
1581
|
for (let at = 0; at + 4 <= head.byteLength; at++) {
|
|
1084
1582
|
if (startsLikeZip(head.subarray(at))) {
|
|
1085
|
-
return
|
|
1583
|
+
return SESSION_NAMES.some((r) => r.test(decodeEntryName(head.subarray(at)))) ? 0.95 : 0;
|
|
1086
1584
|
}
|
|
1087
1585
|
}
|
|
1088
1586
|
return 0;
|
|
1089
1587
|
}
|
|
1090
1588
|
|
|
1091
|
-
/**
|
|
1589
|
+
/**
|
|
1590
|
+
* The Cytoscape session importer.
|
|
1591
|
+
* @category Built-in formats
|
|
1592
|
+
*/
|
|
1092
1593
|
export const cysImporter: GraphImporter<CysImportOptions> = Object.freeze({
|
|
1093
1594
|
format: FORMAT,
|
|
1595
|
+
options: Object.freeze(["maxUncompressedBytes", "zAs"]),
|
|
1094
1596
|
extensions: EXTENSIONS,
|
|
1095
1597
|
mimeTypes: MIME_TYPES,
|
|
1096
1598
|
sniff: sniffCys,
|