@graphty/graph-io 0.3.24 → 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
|
@@ -46,21 +46,26 @@ import {
|
|
|
46
46
|
DUPLICATE_ATTRIBUTE_CODE,
|
|
47
47
|
DUPLICATE_EDGE_ID_CODE,
|
|
48
48
|
DUPLICATE_NODE_CODE,
|
|
49
|
+
ELEMENT_ISSUE,
|
|
49
50
|
EMPTY_INPUT_CODE,
|
|
50
51
|
ENCODING_FALLBACK_CODE,
|
|
51
52
|
GRAPH_NOT_FOUND_CODE,
|
|
52
53
|
ID_MERGED_CODE,
|
|
53
54
|
ID_TEXT_TYPE_CODE,
|
|
55
|
+
INPUT_ISSUE,
|
|
54
56
|
INVALID_ENCODING_CODE,
|
|
55
57
|
INVALID_UTF8_CODE,
|
|
58
|
+
JSON_NONSTANDARD_NUMBER_CODE,
|
|
56
59
|
MISSING_ENDPOINT_CODE,
|
|
57
60
|
MISSING_ID_CODE,
|
|
61
|
+
MULTI_ASPECT_FRAGMENT_CODE,
|
|
58
62
|
MULTIPLE_GRAPHS_CODE,
|
|
59
63
|
NO_GRAPH_CODE,
|
|
60
64
|
OPTION_IGNORED_CODE,
|
|
61
65
|
PARENT_CYCLE_CODE,
|
|
62
66
|
PRECISION_CODE,
|
|
63
67
|
ROLE_TAKEN_CODE,
|
|
68
|
+
SINGLE_OBJECT_ASPECT_CODE,
|
|
64
69
|
SINK_OPTION_CODE,
|
|
65
70
|
STATUS_FAILED_CODE,
|
|
66
71
|
STATUS_WARNING_CODE,
|
|
@@ -68,6 +73,7 @@ import {
|
|
|
68
73
|
SYNTAX_CODE,
|
|
69
74
|
TOO_LARGE_CODE,
|
|
70
75
|
UNKNOWN_ATTR_TYPE_CODE,
|
|
76
|
+
UNKNOWN_ELEMENT_CODE,
|
|
71
77
|
UNKNOWN_ENCODING_CODE,
|
|
72
78
|
UNKNOWN_PARENT_CODE,
|
|
73
79
|
WIDENED_CODE,
|
|
@@ -80,24 +86,30 @@ import {
|
|
|
80
86
|
CxStructure,
|
|
81
87
|
declareFresh,
|
|
82
88
|
ExactInteger,
|
|
89
|
+
fitsF32,
|
|
83
90
|
flipY,
|
|
91
|
+
headText,
|
|
84
92
|
inexactLiteral,
|
|
85
93
|
isRecord,
|
|
86
94
|
JsonScanError,
|
|
95
|
+
keptPrecision,
|
|
87
96
|
plainJson,
|
|
88
97
|
positionDecl,
|
|
98
|
+
reportSharedBlock,
|
|
89
99
|
reportTooDeep,
|
|
90
100
|
scanAspects,
|
|
91
101
|
zDecl,
|
|
92
102
|
} from "../../common/json-elements.js";
|
|
93
103
|
import {
|
|
94
104
|
chooseGraph,
|
|
105
|
+
graphChosen,
|
|
95
106
|
type ImportFormatDefaults,
|
|
96
107
|
reportSinkOptions,
|
|
97
108
|
reportUnusedOptions,
|
|
98
109
|
type ResolvedImportOptions,
|
|
99
110
|
resolveImportOptions,
|
|
100
111
|
} from "../../common/options.js";
|
|
112
|
+
import { agree, plural } from "../../common/plural.js";
|
|
101
113
|
import { ImportReportBuilder } from "../../common/report.js";
|
|
102
114
|
import { weightFromValue } from "../../common/weights.js";
|
|
103
115
|
import {
|
|
@@ -108,7 +120,7 @@ import {
|
|
|
108
120
|
type ImportInput,
|
|
109
121
|
type ImportReport,
|
|
110
122
|
} from "../../types.js";
|
|
111
|
-
import { zAsOption } from "../cx2/importer.js";
|
|
123
|
+
import { ORIGINAL_ID_ATTRIBUTE, zAsOption } from "../cx2/importer.js";
|
|
112
124
|
|
|
113
125
|
/** The format name. */
|
|
114
126
|
const CX_FORMAT = "cx";
|
|
@@ -116,36 +128,46 @@ const CX_FORMAT = "cx";
|
|
|
116
128
|
/** The origin namespace of the per-element visual property columns. */
|
|
117
129
|
const CX_BYPASS_NAMESPACE = "cx.bypass";
|
|
118
130
|
|
|
119
|
-
/**
|
|
120
|
-
|
|
131
|
+
/**
|
|
132
|
+
* The format-specific options of the CX importer.
|
|
133
|
+
* @category Built-in formats
|
|
134
|
+
*/
|
|
135
|
+
export interface CxImportOptions extends GraphChoiceOptions, CommonImportOptions {
|
|
121
136
|
/**
|
|
122
|
-
* Where
|
|
123
|
-
*
|
|
137
|
+
* Where Cytoscape's `z` value (a drawing order, not a depth) goes: "column" keeps it as a node
|
|
138
|
+
* attribute named `z`; "position" makes it the third coordinate of the position.
|
|
139
|
+
* @defaultValue "column"
|
|
124
140
|
*/
|
|
125
141
|
zAs?: "column" | "position" | undefined;
|
|
126
142
|
}
|
|
127
143
|
|
|
128
144
|
/**
|
|
129
|
-
* The issue codes the CX importer records
|
|
130
|
-
* the other importers
|
|
145
|
+
* The issue codes the CX importer records, by name: the codes shared with
|
|
146
|
+
* the other importers and the CX-specific ones. A key is the code without
|
|
131
147
|
* its severity and format prefixes.
|
|
148
|
+
* @category Built-in formats
|
|
132
149
|
*/
|
|
133
150
|
export const CX_ISSUE = Object.freeze({
|
|
134
|
-
|
|
151
|
+
...INPUT_ISSUE,
|
|
152
|
+
...ELEMENT_ISSUE,
|
|
153
|
+
/** The input is empty. The import stops. */
|
|
135
154
|
EMPTY_INPUT: EMPTY_INPUT_CODE,
|
|
136
|
-
/** The text is not JSON
|
|
155
|
+
/** The text is not valid JSON. The import stops. */
|
|
137
156
|
SYNTAX: SYNTAX_CODE,
|
|
138
|
-
/**
|
|
157
|
+
/** The input is not valid UTF-8. The import stops. */
|
|
139
158
|
INVALID_UTF8: INVALID_UTF8_CODE,
|
|
140
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* Some bytes are not valid in the encoding that was chosen (by a byte order mark or the `encoding` option). The
|
|
161
|
+
* import stops.
|
|
162
|
+
*/
|
|
141
163
|
INVALID_ENCODING: INVALID_ENCODING_CODE,
|
|
142
164
|
/** Bytes that are not UTF-8 were read as windows-1252. */
|
|
143
165
|
ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
|
|
144
166
|
/** An encoding the platform cannot decode was ignored. */
|
|
145
167
|
UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
|
|
146
|
-
/** The document is not a CX array, or it is CX2 (
|
|
168
|
+
/** The document is not a CX array, or it is CX2 (the message says so; read it as `cx2`). The import stops. */
|
|
147
169
|
NOT_CX: "E_CX_NOT_CX",
|
|
148
|
-
/**
|
|
170
|
+
/** NumberVerification holds another value than 2^48 - 1, or comes twice. */
|
|
149
171
|
NUMBER_VERIFICATION: "W_CX_NUMBER_VERIFICATION",
|
|
150
172
|
/** An old Cytoscape aspect name (visualProperties, subNetworks, ...) read under its cy name. */
|
|
151
173
|
OLD_ASPECT_NAME: "W_CX_OLD_ASPECT_NAME",
|
|
@@ -153,11 +175,14 @@ export const CX_ISSUE = Object.freeze({
|
|
|
153
175
|
GROUP_NODE_ADDED: "W_CX_GROUP_NODE_ADDED",
|
|
154
176
|
/** Nodes or edges of the root network that no subnetwork holds: Cytoscape shows them in no network; not read. */
|
|
155
177
|
ROOT_ONLY: "W_CX_ROOT_ONLY",
|
|
156
|
-
/** The
|
|
178
|
+
/** The program that wrote the file marked it as failed, so it is incomplete. The import stops. */
|
|
157
179
|
STATUS_FAILED: STATUS_FAILED_CODE,
|
|
158
|
-
/** The
|
|
180
|
+
/** The program that wrote the file marked it as successful but added an error message; the message is shown. */
|
|
159
181
|
STATUS_WARNING: STATUS_WARNING_CODE,
|
|
160
|
-
/**
|
|
182
|
+
/**
|
|
183
|
+
* A member of the file's top-level array that is not a block with one key (`{"nodes": [...]}`), or an element of a
|
|
184
|
+
* block that is not an object. It is skipped; the rest of the file is read.
|
|
185
|
+
*/
|
|
161
186
|
BAD_ASPECT_BLOCK: BAD_ASPECT_BLOCK_CODE,
|
|
162
187
|
/** An aspect after the post-metadata or after the status, a third metaData. */
|
|
163
188
|
ASPECT_ORDER: ASPECT_ORDER_CODE,
|
|
@@ -165,7 +190,10 @@ export const CX_ISSUE = Object.freeze({
|
|
|
165
190
|
COUNT_MISMATCH: COUNT_MISMATCH_CODE,
|
|
166
191
|
/** A value that does not parse as its data type; the cell is unset. */
|
|
167
192
|
BAD_VALUE: BAD_VALUE_CODE,
|
|
168
|
-
/**
|
|
193
|
+
/**
|
|
194
|
+
* An attribute's type was widened because a later value did not fit: an integer above 2^31 in an integer column,
|
|
195
|
+
* or two declared types for one attribute.
|
|
196
|
+
*/
|
|
169
197
|
WIDENED: WIDENED_CODE,
|
|
170
198
|
/** A data type CX does not define; the value is kept as text. */
|
|
171
199
|
UNKNOWN_ATTR_TYPE: UNKNOWN_ATTR_TYPE_CODE,
|
|
@@ -177,9 +205,9 @@ export const CX_ISSUE = Object.freeze({
|
|
|
177
205
|
UNKNOWN_PARENT: UNKNOWN_PARENT_CODE,
|
|
178
206
|
/** A group membership that would close a parent cycle; dropped. */
|
|
179
207
|
PARENT_CYCLE: PARENT_CYCLE_CODE,
|
|
180
|
-
/** The style rules of cyVisualProperties are not applied
|
|
208
|
+
/** The style rules of cyVisualProperties are not applied; they are kept so a CX export writes them back. */
|
|
181
209
|
STYLES_NOT_IMPORTED: STYLES_NOT_IMPORTED_CODE,
|
|
182
|
-
/** A node without an
|
|
210
|
+
/** A node without an id (its `@id` key). The node is skipped. */
|
|
183
211
|
MISSING_ID: MISSING_ID_CODE,
|
|
184
212
|
/** An edge without s or t. */
|
|
185
213
|
MISSING_ENDPOINT: MISSING_ENDPOINT_CODE,
|
|
@@ -189,37 +217,63 @@ export const CX_ISSUE = Object.freeze({
|
|
|
189
217
|
DUPLICATE_EDGE_ID: DUPLICATE_EDGE_ID_CODE,
|
|
190
218
|
/** An id that is not an integer. */
|
|
191
219
|
INVALID_ID: "E_INVALID_ID",
|
|
220
|
+
/**
|
|
221
|
+
* A node or edge key CX does not define, an aspect graph-io keeps for writing back, or a table CX does not define;
|
|
222
|
+
* it is skipped.
|
|
223
|
+
*/
|
|
224
|
+
UNKNOWN_ELEMENT: UNKNOWN_ELEMENT_CODE,
|
|
225
|
+
/** A member holding several aspects; each array-valued key is read as its own fragment. */
|
|
226
|
+
MULTI_ASPECT_FRAGMENT: MULTI_ASPECT_FRAGMENT_CODE,
|
|
227
|
+
/** An aspect written as one object, not an array of elements; read as one element. */
|
|
228
|
+
SINGLE_OBJECT_ASPECT: SINGLE_OBJECT_ASPECT_CODE,
|
|
229
|
+
/** The bare tokens NaN / Infinity / -Infinity (Python's json writes them), read as numbers. */
|
|
230
|
+
JSON_NONSTANDARD_NUMBER: JSON_NONSTANDARD_NUMBER_CODE,
|
|
192
231
|
/** An edge endpoint naming no node of the graph (addMissingNodes false, the default). */
|
|
193
232
|
UNKNOWN_NODE: "E_UNKNOWN_NODE",
|
|
194
233
|
/** A weight that is not a number. */
|
|
195
234
|
INVALID_WEIGHT: "E_INVALID_WEIGHT",
|
|
196
235
|
/** An id spelled as a string or a non-integer literal; read as the integer. */
|
|
197
236
|
ID_TEXT_TYPE: ID_TEXT_TYPE_CODE,
|
|
198
|
-
/** An integer beyond 2^53
|
|
237
|
+
/** An integer beyond 2^53 was stored as the nearest 64-bit float; pass `long: "string"` to keep every digit. */
|
|
199
238
|
PRECISION: PRECISION_CODE,
|
|
200
239
|
/** Two id texts merged under `ids: "number"`. */
|
|
201
240
|
ID_MERGED: ID_MERGED_CODE,
|
|
202
|
-
/**
|
|
241
|
+
/**
|
|
242
|
+
* The file holds several graphs and only the first was read. It is not added when `graphIndex` or `graphName` chose the graph.
|
|
243
|
+
* `importAllGraphs()` reads every one.
|
|
244
|
+
*/
|
|
203
245
|
MULTIPLE_GRAPHS: MULTIPLE_GRAPHS_CODE,
|
|
204
|
-
/** graphIndex or graphName
|
|
246
|
+
/** `graphIndex` or `graphName` matches no subnetwork. The import stops. */
|
|
205
247
|
GRAPH_NOT_FOUND: GRAPH_NOT_FOUND_CODE,
|
|
206
|
-
/** graphName
|
|
248
|
+
/** `graphName` matches several subnetworks. The import stops. */
|
|
207
249
|
AMBIGUOUS_GRAPH_NAME: AMBIGUOUS_GRAPH_NAME_CODE,
|
|
208
|
-
/** The input holds no graph
|
|
250
|
+
/** The input holds no graph. The import stops. */
|
|
209
251
|
NO_GRAPH: NO_GRAPH_CODE,
|
|
210
|
-
/**
|
|
252
|
+
/**
|
|
253
|
+
* An attribute was renamed `<name>#<suffix>` because another attribute already has its name, for example
|
|
254
|
+
* two attributes declared with the same name.
|
|
255
|
+
*/
|
|
211
256
|
COLUMN_RENAMED: COLUMN_RENAMED_CODE,
|
|
212
|
-
/**
|
|
257
|
+
/**
|
|
258
|
+
* You read into a graph builder that already has an id, label or position attribute, so this file's one is kept as
|
|
259
|
+
* a plain attribute.
|
|
260
|
+
*/
|
|
213
261
|
ROLE_TAKEN: ROLE_TAKEN_CODE,
|
|
214
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* You read into a graph builder whose direction is already set, or which already holds edges, so the file is read
|
|
264
|
+
* with the builder's direction instead of its own.
|
|
265
|
+
*/
|
|
215
266
|
DIRECTION_REFUSED: DIRECTION_REFUSED_CODE,
|
|
216
|
-
/** Edges
|
|
267
|
+
/** Edges of the other direction were read with the direction `onMixedDirection` chose. */
|
|
217
268
|
DIRECTION_FORCED: DIRECTION_FORCED_CODE,
|
|
218
|
-
/**
|
|
269
|
+
/** You set an option this format does not use; it had no effect. The message names the option. */
|
|
219
270
|
OPTION_IGNORED: OPTION_IGNORED_CODE,
|
|
220
|
-
/**
|
|
271
|
+
/**
|
|
272
|
+
* You read into your own graph builder, which was created with a different `addMissingNodes`, `duplicateEdges`,
|
|
273
|
+
* `selfLoops` or `weightDtype` than the option you passed; the builder's setting applies.
|
|
274
|
+
*/
|
|
221
275
|
SINK_OPTION: SINK_OPTION_CODE,
|
|
222
|
-
/** The input is
|
|
276
|
+
/** The input is larger than graph-io's size limit. The import stops. */
|
|
223
277
|
TOO_LARGE: TOO_LARGE_CODE,
|
|
224
278
|
});
|
|
225
279
|
|
|
@@ -232,6 +286,7 @@ const USED_OPTIONS: ReadonlySet<keyof CommonImportOptions> = new Set<keyof Commo
|
|
|
232
286
|
"weightFrom",
|
|
233
287
|
"weightDtype",
|
|
234
288
|
"long",
|
|
289
|
+
"restoreMangledIds",
|
|
235
290
|
"errorLimit",
|
|
236
291
|
"signal",
|
|
237
292
|
"onProgress",
|
|
@@ -251,12 +306,21 @@ const ABORT_CHECK_INTERVAL = 64;
|
|
|
251
306
|
const NUMBER_VERIFICATION_VALUES: ReadonlySet<string> = new Set(["281474976710655", "9223372036854775807"]);
|
|
252
307
|
|
|
253
308
|
/** Old Cytoscape aspect names and the names they are read under. */
|
|
254
|
-
const OLD_ASPECT_NAMES:
|
|
255
|
-
visualProperties
|
|
256
|
-
subNetworks
|
|
257
|
-
networkRelations
|
|
258
|
-
hiddenAttributes
|
|
259
|
-
|
|
309
|
+
const OLD_ASPECT_NAMES: ReadonlyMap<string, string> = new Map([
|
|
310
|
+
["visualProperties", "cyVisualProperties"],
|
|
311
|
+
["subNetworks", "cySubNetworks"],
|
|
312
|
+
["networkRelations", "cyNetworkRelations"],
|
|
313
|
+
["hiddenAttributes", "cyHiddenAttributes"],
|
|
314
|
+
]);
|
|
315
|
+
|
|
316
|
+
/** The keys a nodes element defines. */
|
|
317
|
+
const NODE_KEYS: ReadonlySet<string> = new Set(["@id", "n", "r"]);
|
|
318
|
+
|
|
319
|
+
/** The keys an edges element defines. */
|
|
320
|
+
const EDGE_KEYS: ReadonlySet<string> = new Set(["@id", "s", "t", "i"]);
|
|
321
|
+
|
|
322
|
+
/** The tables cyTableColumn applies to. */
|
|
323
|
+
const TABLES: ReadonlySet<string> = new Set(["node_table", "edge_table", "network_table"]);
|
|
260
324
|
|
|
261
325
|
/** The aspects the importer reads into the graph (everything else is kept verbatim). */
|
|
262
326
|
const READ_ASPECTS: ReadonlySet<string> = new Set([
|
|
@@ -299,6 +363,9 @@ const ALSO_KEPT: ReadonlySet<string> = new Set([
|
|
|
299
363
|
/** Cytoscape's table-cell style aspects (both spellings): style rules, kept and reported, never read. */
|
|
300
364
|
const TABLE_STYLE_ASPECTS: readonly string[] = ["tableVisualProperties", "cyTableVisualProperties"];
|
|
301
365
|
|
|
366
|
+
/** The importer's own entries of meta.extra.cx, which an aspect of the same name would collide with. */
|
|
367
|
+
const OWN_EXTRA_KEYS: readonly string[] = ["groups", "subnetwork"];
|
|
368
|
+
|
|
302
369
|
/** The first keys that make a head CX for sure, and the other aspect names a CX document may start with. */
|
|
303
370
|
const CX_FIRST_KEYS: readonly string[] = ["numberVerification", "metaData"];
|
|
304
371
|
const CX_ASPECT_KEYS: readonly string[] = [
|
|
@@ -339,14 +406,14 @@ interface CxType {
|
|
|
339
406
|
}
|
|
340
407
|
|
|
341
408
|
/** The CX data types, with the Java reader's float aliases. */
|
|
342
|
-
const SCALARS:
|
|
343
|
-
string
|
|
344
|
-
boolean
|
|
345
|
-
integer
|
|
346
|
-
long
|
|
347
|
-
double
|
|
348
|
-
float
|
|
349
|
-
|
|
409
|
+
const SCALARS: ReadonlyMap<string, CxScalar> = new Map([
|
|
410
|
+
["string", "string"],
|
|
411
|
+
["boolean", "boolean"],
|
|
412
|
+
["integer", "integer"],
|
|
413
|
+
["long", "long"],
|
|
414
|
+
["double", "double"],
|
|
415
|
+
["float", "double"],
|
|
416
|
+
]);
|
|
350
417
|
|
|
351
418
|
/**
|
|
352
419
|
* Resolve a `d` value.
|
|
@@ -361,7 +428,7 @@ function cxType(d: unknown): CxType | null {
|
|
|
361
428
|
return null;
|
|
362
429
|
}
|
|
363
430
|
const list = d.startsWith("list_of_");
|
|
364
|
-
const scalar = SCALARS
|
|
431
|
+
const scalar = SCALARS.get(list ? d.slice("list_of_".length) : d);
|
|
365
432
|
return scalar === undefined ? null : { scalar, list };
|
|
366
433
|
}
|
|
367
434
|
|
|
@@ -492,7 +559,15 @@ function parseScalar(
|
|
|
492
559
|
if (value === "Infinity" || value === "-Infinity") {
|
|
493
560
|
return Number(value);
|
|
494
561
|
}
|
|
495
|
-
|
|
562
|
+
if (!DECIMAL_TEXT.test(value)) {
|
|
563
|
+
return BAD;
|
|
564
|
+
}
|
|
565
|
+
const n = Number(value);
|
|
566
|
+
if (!Number.isFinite(n)) {
|
|
567
|
+
// beyond the range of a double: Java's parseDouble gives Infinity too, but say so
|
|
568
|
+
onPrecision(value);
|
|
569
|
+
}
|
|
570
|
+
return n;
|
|
496
571
|
}
|
|
497
572
|
}
|
|
498
573
|
}
|
|
@@ -653,15 +728,16 @@ function aspect(doc: CxDocument, name: string): readonly Held[] {
|
|
|
653
728
|
* The inexact-literal bits of an element.
|
|
654
729
|
* @param text - the element's JSON text
|
|
655
730
|
* @param keys - the id keys to check, bit 1, 2, 4 in order
|
|
731
|
+
* @param depth - the depth of the element's own keys in the text (2: a member's single object)
|
|
656
732
|
* @returns the bits
|
|
657
733
|
*/
|
|
658
|
-
function inexactBits(text: string, keys: readonly string[]): number {
|
|
734
|
+
function inexactBits(text: string, keys: readonly string[], depth = 1): number {
|
|
659
735
|
if (!/[0-9][.eE]/.test(text)) {
|
|
660
736
|
return 0;
|
|
661
737
|
}
|
|
662
738
|
let bits = 0;
|
|
663
739
|
keys.forEach((key, i) => {
|
|
664
|
-
if (inexactLiteral(text, key)) {
|
|
740
|
+
if (inexactLiteral(text, key, depth)) {
|
|
665
741
|
bits |= 1 << i;
|
|
666
742
|
}
|
|
667
743
|
});
|
|
@@ -709,6 +785,117 @@ function checkNumberVerification(value: unknown, count: number, report: ImportRe
|
|
|
709
785
|
}
|
|
710
786
|
}
|
|
711
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Whether an aspect name is one CX (or NDEx, or Cytoscape) defines.
|
|
790
|
+
* @param name - the aspect name as written
|
|
791
|
+
* @returns true for a known aspect
|
|
792
|
+
*/
|
|
793
|
+
function isCxAspect(name: string): boolean {
|
|
794
|
+
return (
|
|
795
|
+
READ_ASPECTS.has(name) ||
|
|
796
|
+
OLD_ASPECT_NAMES.has(name) ||
|
|
797
|
+
CX_ASPECT_KEYS.includes(name) ||
|
|
798
|
+
TABLE_STYLE_ASPECTS.includes(name)
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/** What collectElement() needs across elements. */
|
|
803
|
+
interface CollectState {
|
|
804
|
+
readonly doc: CxDocument;
|
|
805
|
+
readonly report: ImportReportBuilder;
|
|
806
|
+
/** numberVerification elements read so far. */
|
|
807
|
+
verifications: number;
|
|
808
|
+
/** The precision callback of the kept aspects. */
|
|
809
|
+
readonly onKeptPrecision: (digits: string) => void;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/** One parsed aspect element and where it came from. */
|
|
813
|
+
interface CollectedElement {
|
|
814
|
+
readonly name: string;
|
|
815
|
+
readonly value: unknown;
|
|
816
|
+
readonly text: string;
|
|
817
|
+
readonly line: number;
|
|
818
|
+
readonly exact: boolean;
|
|
819
|
+
/** The depth of the element's own keys in text (2 for a member's single object). */
|
|
820
|
+
readonly depth: number;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/** The id keys of the aspects whose ids are checked for precision. */
|
|
824
|
+
const ID_KEYS: ReadonlyMap<string, readonly string[]> = new Map([
|
|
825
|
+
["nodes", ["@id"]],
|
|
826
|
+
["edges", ["@id", "s", "t"]],
|
|
827
|
+
]);
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* File an attribute element by its attribute name n; one without a usable name is reported and skipped.
|
|
831
|
+
* @param table - the aspect's attribute table
|
|
832
|
+
* @param name - the aspect
|
|
833
|
+
* @param held - the element
|
|
834
|
+
* @param report - the report
|
|
835
|
+
*/
|
|
836
|
+
function fileAttribute(table: Map<string, Held[]>, name: string, held: Held, report: ImportReportBuilder): void {
|
|
837
|
+
const { value, line } = held;
|
|
838
|
+
const n = isRecord(value) ? value.n : undefined;
|
|
839
|
+
if (n === undefined || n === null) {
|
|
840
|
+
report.error("missing-value", MISSING_ID_CODE, `a ${name} element has no attribute name n; skipped`, {
|
|
841
|
+
line,
|
|
842
|
+
element: name,
|
|
843
|
+
});
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (typeof n !== "string" || n === "") {
|
|
847
|
+
report.error(
|
|
848
|
+
"validation-error",
|
|
849
|
+
BAD_VALUE_CODE,
|
|
850
|
+
`a ${name} element has the attribute name n ${shown(n)}, not a non-empty string; skipped`,
|
|
851
|
+
{ line, element: name },
|
|
852
|
+
);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (!table.has(n)) {
|
|
856
|
+
table.set(n, []);
|
|
857
|
+
}
|
|
858
|
+
table.get(n)?.push(held);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* File one parsed aspect element: kept verbatim, checked (numberVerification), filed by attribute
|
|
863
|
+
* name (an attribute element without a usable name n is reported and skipped) or by aspect.
|
|
864
|
+
* @param state - the document being collected
|
|
865
|
+
* @param element - the element
|
|
866
|
+
*/
|
|
867
|
+
function collectElement(state: CollectState, element: CollectedElement): void {
|
|
868
|
+
const { doc, report } = state;
|
|
869
|
+
const { name, value, text, line, exact, depth } = element;
|
|
870
|
+
doc.structure.element(name, value, line);
|
|
871
|
+
if (ALSO_KEPT.has(name) || !READ_ASPECTS.has(name)) {
|
|
872
|
+
if (!doc.kept.has(name)) {
|
|
873
|
+
doc.kept.set(name, []);
|
|
874
|
+
}
|
|
875
|
+
// numberVerification holds Java's Long.MAX_VALUE in every Cytoscape file: checked, never warned
|
|
876
|
+
const onPrecision = name === "numberVerification" ? undefined : state.onKeptPrecision;
|
|
877
|
+
doc.kept.get(name)?.push(exact ? plainJson(value, onPrecision) : value);
|
|
878
|
+
}
|
|
879
|
+
if (!READ_ASPECTS.has(name) || name === "metaData" || name === "status") {
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
if (name === "numberVerification") {
|
|
883
|
+
checkNumberVerification(value, ++state.verifications, report, line);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
const ids = ID_KEYS.get(name);
|
|
887
|
+
const held: Held = { value, line, inexact: ids === undefined ? 0 : inexactBits(text, ids, depth) };
|
|
888
|
+
const table = attributeTable(doc, name);
|
|
889
|
+
if (table !== null) {
|
|
890
|
+
fileAttribute(table, name, held, report);
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
if (!doc.aspects.has(name)) {
|
|
894
|
+
doc.aspects.set(name, []);
|
|
895
|
+
}
|
|
896
|
+
doc.aspects.get(name)?.push(held);
|
|
897
|
+
}
|
|
898
|
+
|
|
712
899
|
/**
|
|
713
900
|
* Read the whole document through the streaming scanner, checking its structure.
|
|
714
901
|
* @param input - the input
|
|
@@ -721,17 +908,37 @@ async function readDocument(
|
|
|
721
908
|
report: ImportReportBuilder,
|
|
722
909
|
options: ResolvedImportOptions,
|
|
723
910
|
): Promise<CxDocument> {
|
|
724
|
-
const structure = new CxStructure(report);
|
|
911
|
+
const structure = new CxStructure(report, (name) => OLD_ASPECT_NAMES.get(name) ?? name);
|
|
725
912
|
const doc: CxDocument = {
|
|
726
913
|
aspects: new Map(),
|
|
727
914
|
attributes: { node: new Map(), edge: new Map(), network: new Map() },
|
|
728
915
|
kept: new Map(),
|
|
729
916
|
structure,
|
|
730
917
|
};
|
|
918
|
+
const state: CollectState = {
|
|
919
|
+
doc,
|
|
920
|
+
report,
|
|
921
|
+
verifications: 0,
|
|
922
|
+
onKeptPrecision: keptPrecision(report, "meta.extra.cx"),
|
|
923
|
+
};
|
|
731
924
|
let sinceCheck = 0;
|
|
732
|
-
let
|
|
925
|
+
let members = 0;
|
|
926
|
+
let known = 0;
|
|
927
|
+
const unknownNames = new Set<string>();
|
|
928
|
+
const lastMember = { block: -1 };
|
|
929
|
+
const countMember = (block: number, name: string | null): void => {
|
|
930
|
+
if (block !== lastMember.block) {
|
|
931
|
+
lastMember.block = block;
|
|
932
|
+
members++;
|
|
933
|
+
}
|
|
934
|
+
if (name !== null && isCxAspect(name)) {
|
|
935
|
+
known++;
|
|
936
|
+
} else if (name !== null) {
|
|
937
|
+
unknownNames.add(name);
|
|
938
|
+
}
|
|
939
|
+
};
|
|
733
940
|
const canonical = (name: string, line: number): string => {
|
|
734
|
-
const renamed = OLD_ASPECT_NAMES
|
|
941
|
+
const renamed = OLD_ASPECT_NAMES.get(name);
|
|
735
942
|
if (renamed === undefined) {
|
|
736
943
|
return name;
|
|
737
944
|
}
|
|
@@ -744,51 +951,11 @@ async function readDocument(
|
|
|
744
951
|
);
|
|
745
952
|
return renamed;
|
|
746
953
|
};
|
|
747
|
-
const collect = (name: string, value: unknown, text: string, line: number, exact: boolean): void => {
|
|
748
|
-
|
|
749
|
-
if (ALSO_KEPT.has(name) || !READ_ASPECTS.has(name)) {
|
|
750
|
-
if (!doc.kept.has(name)) {
|
|
751
|
-
doc.kept.set(name, []);
|
|
752
|
-
}
|
|
753
|
-
doc.kept.get(name)?.push(exact ? plainJson(value) : value);
|
|
754
|
-
}
|
|
755
|
-
if (!READ_ASPECTS.has(name) || name === "metaData" || name === "status") {
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
if (name === "numberVerification") {
|
|
759
|
-
checkNumberVerification(value, ++verifications, report, line);
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
762
|
-
let inexact = 0;
|
|
763
|
-
if (name === "nodes") {
|
|
764
|
-
inexact = inexactBits(text, ["@id"]);
|
|
765
|
-
} else if (name === "edges") {
|
|
766
|
-
inexact = inexactBits(text, ["@id", "s", "t"]);
|
|
767
|
-
}
|
|
768
|
-
const held: Held = { value, line, inexact };
|
|
769
|
-
const table = attributeTable(doc, name);
|
|
770
|
-
if (table !== null) {
|
|
771
|
-
if (!isRecord(value) || typeof value.n !== "string") {
|
|
772
|
-
report.error("missing-value", MISSING_ID_CODE, `a ${name} element has no attribute name n; skipped`, {
|
|
773
|
-
line,
|
|
774
|
-
element: name,
|
|
775
|
-
});
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
const key = value.n;
|
|
779
|
-
if (!table.has(key)) {
|
|
780
|
-
table.set(key, []);
|
|
781
|
-
}
|
|
782
|
-
table.get(key)?.push(held);
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
if (!doc.aspects.has(name)) {
|
|
786
|
-
doc.aspects.set(name, []);
|
|
787
|
-
}
|
|
788
|
-
doc.aspects.get(name)?.push(held);
|
|
954
|
+
const collect = (name: string, value: unknown, text: string, line: number, exact: boolean, depth = 1): void => {
|
|
955
|
+
collectElement(state, { name, value, text, line, exact, depth });
|
|
789
956
|
};
|
|
790
957
|
try {
|
|
791
|
-
for await (const event of scanAspects(textChunks(input, report, options))) {
|
|
958
|
+
for await (const event of scanAspects(textChunks(input, report, options), report)) {
|
|
792
959
|
if (++sinceCheck >= ABORT_CHECK_INTERVAL) {
|
|
793
960
|
sinceCheck = 0;
|
|
794
961
|
throwIfAborted(options.signal);
|
|
@@ -797,7 +964,7 @@ async function readDocument(
|
|
|
797
964
|
case "root":
|
|
798
965
|
report.fail(
|
|
799
966
|
CX_ISSUE.NOT_CX,
|
|
800
|
-
`a CX document is a JSON array of aspects; found ${
|
|
967
|
+
`a CX document is a JSON array of aspects; found ${event.object ? "an object" : shown(event.value)}`,
|
|
801
968
|
{ line: event.line },
|
|
802
969
|
);
|
|
803
970
|
break;
|
|
@@ -812,11 +979,21 @@ async function readDocument(
|
|
|
812
979
|
}
|
|
813
980
|
const keys = isRecord(value) ? Object.keys(value) : [];
|
|
814
981
|
if (isRecord(value) && keys.length === 1 && isRecord(value[keys[0]])) {
|
|
982
|
+
countMember(event.block, keys[0]);
|
|
815
983
|
const name = canonical(keys[0], event.line);
|
|
984
|
+
if (isCxAspect(name)) {
|
|
985
|
+
report.warning(
|
|
986
|
+
"validation-error",
|
|
987
|
+
CX_ISSUE.SINGLE_OBJECT_ASPECT,
|
|
988
|
+
`the "${keys[0]}" aspect is one object, not an array of elements; read as one element`,
|
|
989
|
+
{ line: event.line, element: keys[0] },
|
|
990
|
+
);
|
|
991
|
+
}
|
|
816
992
|
structure.block(name, event.line);
|
|
817
|
-
collect(name, value[keys[0]],
|
|
993
|
+
collect(name, value[keys[0]], event.text, event.line, event.exact, 2);
|
|
818
994
|
break;
|
|
819
995
|
}
|
|
996
|
+
countMember(event.block, null);
|
|
820
997
|
report.error(
|
|
821
998
|
"parse-error",
|
|
822
999
|
BAD_ASPECT_BLOCK_CODE,
|
|
@@ -826,21 +1003,24 @@ async function readDocument(
|
|
|
826
1003
|
break;
|
|
827
1004
|
}
|
|
828
1005
|
case "block":
|
|
1006
|
+
countMember(event.block, event.aspect);
|
|
1007
|
+
reportSharedBlock(report, event);
|
|
829
1008
|
structure.block(canonical(event.aspect, event.line), event.line);
|
|
830
1009
|
break;
|
|
831
1010
|
case "element": {
|
|
832
|
-
const name = OLD_ASPECT_NAMES
|
|
1011
|
+
const name = OLD_ASPECT_NAMES.get(event.aspect) ?? event.aspect;
|
|
833
1012
|
collect(name, event.value, event.text, event.line, event.exact);
|
|
834
1013
|
break;
|
|
835
1014
|
}
|
|
836
1015
|
case "deep":
|
|
1016
|
+
countMember(event.block, event.aspect);
|
|
837
1017
|
reportTooDeep(report, event);
|
|
838
1018
|
break;
|
|
839
1019
|
case "extraKeys":
|
|
840
1020
|
report.error(
|
|
841
1021
|
"parse-error",
|
|
842
1022
|
BAD_ASPECT_BLOCK_CODE,
|
|
843
|
-
`the "${event.aspect}" fragment holds more keys (${event.keys.join(", ")}); a fragment has one key, the others are skipped`,
|
|
1023
|
+
`the "${event.aspect}" fragment holds more keys (${event.keys.join(", ")}) whose values are not arrays; a fragment has one key, the others are skipped`,
|
|
844
1024
|
{ line: event.line, element: event.aspect },
|
|
845
1025
|
);
|
|
846
1026
|
break;
|
|
@@ -854,6 +1034,20 @@ async function readDocument(
|
|
|
854
1034
|
}
|
|
855
1035
|
throw err;
|
|
856
1036
|
}
|
|
1037
|
+
if (members > 0 && known === 0) {
|
|
1038
|
+
const found = [...unknownNames].slice(0, 5).map((n) => JSON.stringify(n));
|
|
1039
|
+
const hint = unknownNames.has("data") ? ": Cytoscape.js elements?" : "";
|
|
1040
|
+
const seen = found.length > 0 ? ` (found ${found.join(", ")}${hint})` : "";
|
|
1041
|
+
report.fail(CX_ISSUE.NOT_CX, `no member of the array is a CX aspect${seen}; the input is not CX`);
|
|
1042
|
+
}
|
|
1043
|
+
if (structure.hasStatus && !structure.statusWellFormed()) {
|
|
1044
|
+
report.error(
|
|
1045
|
+
"validation-error",
|
|
1046
|
+
BAD_VALUE_CODE,
|
|
1047
|
+
`the status element is ${shown(structure.status ?? null)}, not an object with a boolean success; ignored`,
|
|
1048
|
+
{ element: "status" },
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
857
1051
|
structure.checkCounts();
|
|
858
1052
|
return doc;
|
|
859
1053
|
}
|
|
@@ -888,11 +1082,13 @@ function refId(raw: unknown): NodeId | null {
|
|
|
888
1082
|
}
|
|
889
1083
|
|
|
890
1084
|
/**
|
|
891
|
-
* A member list: the ids of an array, or null for "all".
|
|
1085
|
+
* A member list: the ids of an array, or null for "all" (an absent list is empty).
|
|
892
1086
|
* @param raw - the list
|
|
1087
|
+
* @param onBad - told about a list that is neither an array nor "all" (BAD_VALUE) and about a
|
|
1088
|
+
* member that is not an id (INVALID_ID)
|
|
893
1089
|
* @returns the set, or null for every element
|
|
894
1090
|
*/
|
|
895
|
-
function memberSet(raw: unknown): Set<NodeId> | null {
|
|
1091
|
+
function memberSet(raw: unknown, onBad?: (code: string, message: string) => void): Set<NodeId> | null {
|
|
896
1092
|
if (raw === "all") {
|
|
897
1093
|
return null;
|
|
898
1094
|
}
|
|
@@ -900,87 +1096,79 @@ function memberSet(raw: unknown): Set<NodeId> | null {
|
|
|
900
1096
|
if (Array.isArray(raw)) {
|
|
901
1097
|
for (const item of raw) {
|
|
902
1098
|
const id = refId(item);
|
|
903
|
-
if (id
|
|
1099
|
+
if (id === null) {
|
|
1100
|
+
onBad?.(CX_ISSUE.INVALID_ID, `the member ${shown(item)} is not an id; ignored`);
|
|
1101
|
+
} else {
|
|
904
1102
|
out.add(id);
|
|
905
1103
|
}
|
|
906
1104
|
}
|
|
1105
|
+
} else if (raw !== undefined) {
|
|
1106
|
+
onBad?.(BAD_VALUE_CODE, `the member list ${shown(raw)} is neither a list of ids nor "all"; read as empty`);
|
|
907
1107
|
}
|
|
908
1108
|
return out;
|
|
909
1109
|
}
|
|
910
1110
|
|
|
911
1111
|
/**
|
|
912
|
-
* The
|
|
913
|
-
*
|
|
914
|
-
*
|
|
1112
|
+
* The id of an element of a structural aspect (cySubNetworks, cyViews, cyNetworkRelations), or
|
|
1113
|
+
* null with an issue: E_BAD_ASPECT_BLOCK for an element that is not an object, E_MISSING_ID or
|
|
1114
|
+
* E_INVALID_ID for its id.
|
|
1115
|
+
* @param held - the element
|
|
1116
|
+
* @param name - the aspect
|
|
1117
|
+
* @param key - the id key
|
|
1118
|
+
* @param report - the report
|
|
1119
|
+
* @returns the element and its id, or null
|
|
915
1120
|
*/
|
|
916
|
-
function
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
for (const { value } of aspect(doc, "cyNetworkRelations")) {
|
|
935
|
-
if (!isRecord(value)) {
|
|
936
|
-
continue;
|
|
937
|
-
}
|
|
938
|
-
const child = refId(value.c);
|
|
939
|
-
if (child === null) {
|
|
940
|
-
continue;
|
|
941
|
-
}
|
|
942
|
-
if (value.r === "view") {
|
|
943
|
-
const parent = refId(value.p);
|
|
944
|
-
if (parent !== null) {
|
|
945
|
-
addView(parent, child);
|
|
946
|
-
}
|
|
947
|
-
continue;
|
|
948
|
-
}
|
|
949
|
-
if (typeof value.name === "string") {
|
|
950
|
-
relationNames.set(child, value.name);
|
|
951
|
-
}
|
|
952
|
-
if (subs.has(child) && !order.includes(child)) {
|
|
953
|
-
order.push(child);
|
|
954
|
-
}
|
|
1121
|
+
function structuralId(
|
|
1122
|
+
held: Held,
|
|
1123
|
+
name: string,
|
|
1124
|
+
key: string,
|
|
1125
|
+
report: ImportReportBuilder,
|
|
1126
|
+
): { readonly value: Record<string, unknown>; readonly id: NodeId } | null {
|
|
1127
|
+
const { value, line } = held;
|
|
1128
|
+
if (!isRecord(value)) {
|
|
1129
|
+
report.error(
|
|
1130
|
+
"parse-error",
|
|
1131
|
+
BAD_ASPECT_BLOCK_CODE,
|
|
1132
|
+
`a ${name} element is ${shown(value)}, not an object; skipped`,
|
|
1133
|
+
{
|
|
1134
|
+
line,
|
|
1135
|
+
element: name,
|
|
1136
|
+
},
|
|
1137
|
+
);
|
|
1138
|
+
return null;
|
|
955
1139
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1140
|
+
if (value[key] === undefined || value[key] === null) {
|
|
1141
|
+
report.error("missing-value", MISSING_ID_CODE, `a ${name} element has no ${key}; skipped`, {
|
|
1142
|
+
line,
|
|
1143
|
+
element: name,
|
|
1144
|
+
});
|
|
1145
|
+
return null;
|
|
962
1146
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1147
|
+
const id = refId(value[key]);
|
|
1148
|
+
if (id === null) {
|
|
1149
|
+
report.error(
|
|
1150
|
+
"validation-error",
|
|
1151
|
+
CX_ISSUE.INVALID_ID,
|
|
1152
|
+
`a ${name} element has the ${key} ${shown(value[key])}, not an integer id; skipped`,
|
|
1153
|
+
{
|
|
1154
|
+
line,
|
|
1155
|
+
element: name,
|
|
1156
|
+
},
|
|
1157
|
+
);
|
|
1158
|
+
return null;
|
|
967
1159
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
return found;
|
|
983
|
-
};
|
|
1160
|
+
return { value, id };
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* The graphs of a document, in the order Cytoscape opens them.
|
|
1165
|
+
* @param doc - the document
|
|
1166
|
+
* @param report - where malformed subnetworks, views and relations are recorded
|
|
1167
|
+
* @returns one plan per graph (at least one)
|
|
1168
|
+
*/
|
|
1169
|
+
function graphPlans(doc: CxDocument, report: ImportReportBuilder): GraphPlan[] {
|
|
1170
|
+
const subs = readSubnetworks(doc, report);
|
|
1171
|
+
const { relationNames, order, views } = readRelations(doc, subs, report);
|
|
984
1172
|
if (order.length <= 1) {
|
|
985
1173
|
const sub = order.length === 1 ? order[0] : null;
|
|
986
1174
|
const members = sub === null ? undefined : subs.get(sub);
|
|
@@ -991,10 +1179,11 @@ function graphPlans(doc: CxDocument): GraphPlan[] {
|
|
|
991
1179
|
if (graphViews.length === 0) {
|
|
992
1180
|
graphViews = layoutViews(doc);
|
|
993
1181
|
}
|
|
1182
|
+
const relationName = sub === null ? null : (relationNames.get(sub) ?? null);
|
|
994
1183
|
return [
|
|
995
1184
|
{
|
|
996
1185
|
index: 0,
|
|
997
|
-
name:
|
|
1186
|
+
name: relationName ?? networkName(doc, sub),
|
|
998
1187
|
subnetwork: sub,
|
|
999
1188
|
nodes: members?.nodes ?? null,
|
|
1000
1189
|
edges: members?.edges ?? null,
|
|
@@ -1004,7 +1193,7 @@ function graphPlans(doc: CxDocument): GraphPlan[] {
|
|
|
1004
1193
|
}
|
|
1005
1194
|
return order.map((sub, index) => ({
|
|
1006
1195
|
index,
|
|
1007
|
-
name: relationNames.get(sub) ?? networkName(sub),
|
|
1196
|
+
name: relationNames.get(sub) ?? networkName(doc, sub),
|
|
1008
1197
|
subnetwork: sub,
|
|
1009
1198
|
nodes: subs.get(sub)?.nodes ?? null,
|
|
1010
1199
|
edges: subs.get(sub)?.edges ?? null,
|
|
@@ -1012,6 +1201,139 @@ function graphPlans(doc: CxDocument): GraphPlan[] {
|
|
|
1012
1201
|
}));
|
|
1013
1202
|
}
|
|
1014
1203
|
|
|
1204
|
+
/** A subnetwork's members: null when the element does not list them. */
|
|
1205
|
+
type Subnetwork = { nodes: Set<NodeId> | null; edges: Set<NodeId> | null };
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* The subnetworks a document declares (cySubNetworks), by id; a repeated id is reported and skipped.
|
|
1209
|
+
* @param doc - the document
|
|
1210
|
+
* @param report - the report
|
|
1211
|
+
* @returns the subnetworks
|
|
1212
|
+
*/
|
|
1213
|
+
function readSubnetworks(doc: CxDocument, report: ImportReportBuilder): Map<NodeId, Subnetwork> {
|
|
1214
|
+
const subs = new Map<NodeId, Subnetwork>();
|
|
1215
|
+
for (const held of aspect(doc, "cySubNetworks")) {
|
|
1216
|
+
const sub = structuralId(held, "cySubNetworks", "@id", report);
|
|
1217
|
+
if (sub === null) {
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
const { value, id } = sub;
|
|
1221
|
+
if (subs.has(id)) {
|
|
1222
|
+
report.error(
|
|
1223
|
+
"validation-error",
|
|
1224
|
+
BAD_ASPECT_BLOCK_CODE,
|
|
1225
|
+
`a second cySubNetworks element has the @id ${String(id)}; skipped, the first is used`,
|
|
1226
|
+
{ line: held.line, element: "cySubNetworks" },
|
|
1227
|
+
);
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
const onBad = (code: string, message: string): void => {
|
|
1231
|
+
report.error("validation-error", code, `subnetwork ${String(id)}: ${message}`, {
|
|
1232
|
+
line: held.line,
|
|
1233
|
+
element: "cySubNetworks",
|
|
1234
|
+
});
|
|
1235
|
+
};
|
|
1236
|
+
subs.set(id, { nodes: memberSet(value.nodes, onBad), edges: memberSet(value.edges, onBad) });
|
|
1237
|
+
}
|
|
1238
|
+
return subs;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* The network relations of a document: each subnetwork's name, the order Cytoscape opens them in
|
|
1243
|
+
* (the relations' order, then the other subnetworks), and each one's views.
|
|
1244
|
+
* @param doc - the document
|
|
1245
|
+
* @param subs - the subnetworks
|
|
1246
|
+
* @param report - the report
|
|
1247
|
+
* @returns the names, the order and the views
|
|
1248
|
+
*/
|
|
1249
|
+
function readRelations(
|
|
1250
|
+
doc: CxDocument,
|
|
1251
|
+
subs: ReadonlyMap<NodeId, Subnetwork>,
|
|
1252
|
+
report: ImportReportBuilder,
|
|
1253
|
+
): { relationNames: Map<NodeId, string>; order: NodeId[]; views: Map<NodeId, NodeId[]> } {
|
|
1254
|
+
const relationNames = new Map<NodeId, string>();
|
|
1255
|
+
const order: NodeId[] = [];
|
|
1256
|
+
const views = new Map<NodeId, NodeId[]>();
|
|
1257
|
+
const addView = (sub: NodeId, view: NodeId): void => {
|
|
1258
|
+
const list = views.get(sub) ?? [];
|
|
1259
|
+
if (!list.includes(view)) {
|
|
1260
|
+
list.push(view);
|
|
1261
|
+
}
|
|
1262
|
+
views.set(sub, list);
|
|
1263
|
+
};
|
|
1264
|
+
|
|
1265
|
+
for (const held of aspect(doc, "cyNetworkRelations")) {
|
|
1266
|
+
const relation = structuralId(held, "cyNetworkRelations", "c", report);
|
|
1267
|
+
if (relation !== null) {
|
|
1268
|
+
fileRelation(relation, subs, { relationNames, order, addView });
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
for (const held of aspect(doc, "cyViews")) {
|
|
1272
|
+
const view = structuralId(held, "cyViews", "@id", report);
|
|
1273
|
+
const sub = view === null ? null : refId(view.value.s);
|
|
1274
|
+
if (view !== null && sub !== null) {
|
|
1275
|
+
addView(sub, view.id);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
order.push(...[...subs.keys()].filter((id) => !order.includes(id)));
|
|
1279
|
+
return { relationNames, order, views };
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* File one network relation: a view of a subnetwork, or a subnetwork's name and its place in the order.
|
|
1284
|
+
* @param relation - the relation element and its child id
|
|
1285
|
+
* @param relation.value - the element
|
|
1286
|
+
* @param relation.id - the child: a view or a subnetwork
|
|
1287
|
+
* @param subs - the subnetworks
|
|
1288
|
+
* @param out - where the relation is filed
|
|
1289
|
+
* @param out.relationNames - each subnetwork's name
|
|
1290
|
+
* @param out.order - the subnetworks in relation order
|
|
1291
|
+
* @param out.addView - records a view of a subnetwork
|
|
1292
|
+
*/
|
|
1293
|
+
function fileRelation(
|
|
1294
|
+
relation: { readonly value: Record<string, unknown>; readonly id: NodeId },
|
|
1295
|
+
subs: ReadonlyMap<NodeId, Subnetwork>,
|
|
1296
|
+
out: { relationNames: Map<NodeId, string>; order: NodeId[]; addView: (sub: NodeId, view: NodeId) => void },
|
|
1297
|
+
): void {
|
|
1298
|
+
const { value, id: child } = relation;
|
|
1299
|
+
if (value.r === "view") {
|
|
1300
|
+
const parent = refId(value.p);
|
|
1301
|
+
if (parent !== null) {
|
|
1302
|
+
out.addView(parent, child);
|
|
1303
|
+
}
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
if (typeof value.name === "string") {
|
|
1307
|
+
out.relationNames.set(child, value.name);
|
|
1308
|
+
}
|
|
1309
|
+
if (subs.has(child) && !out.order.includes(child)) {
|
|
1310
|
+
out.order.push(child);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* A subnetwork's name from the network attributes: the one scoped to it, else the unscoped one.
|
|
1316
|
+
* @param doc - the document
|
|
1317
|
+
* @param sub - the subnetwork, or null
|
|
1318
|
+
* @returns the name, or null
|
|
1319
|
+
*/
|
|
1320
|
+
function networkName(doc: CxDocument, sub: NodeId | null): string | null {
|
|
1321
|
+
let found: string | null = null;
|
|
1322
|
+
for (const { value } of doc.attributes.network.get("name") ?? []) {
|
|
1323
|
+
if (!isRecord(value) || typeof value.v !== "string") {
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
const scope = value.s === undefined ? null : refId(value.s);
|
|
1327
|
+
if (scope === sub) {
|
|
1328
|
+
return value.v;
|
|
1329
|
+
}
|
|
1330
|
+
if (scope === null) {
|
|
1331
|
+
found ??= value.v;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return found;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1015
1337
|
/**
|
|
1016
1338
|
* The distinct views the layout entries name, in order of appearance.
|
|
1017
1339
|
* @param doc - the document
|
|
@@ -1072,12 +1394,18 @@ class CxReader {
|
|
|
1072
1394
|
/** Every edge id of the root network. */
|
|
1073
1395
|
private readonly rootEdges = new Set<NodeId>();
|
|
1074
1396
|
|
|
1075
|
-
/** CX node id -> sink node index, for this graph. */
|
|
1397
|
+
/** CX node id (as written, before the `ids` option) -> sink node index, for this graph. */
|
|
1076
1398
|
private readonly nodeRows = new Map<NodeId, number>();
|
|
1077
1399
|
|
|
1078
|
-
/**
|
|
1400
|
+
/** Sink node id -> sink node index: two CX ids the `ids` option makes one are one node. */
|
|
1401
|
+
private readonly sinkRows = new Map<NodeId, number>();
|
|
1402
|
+
|
|
1403
|
+
/** CX edge id (as written) -> sink edge index, for this graph. */
|
|
1079
1404
|
private readonly edgeRows = new Map<NodeId, number>();
|
|
1080
1405
|
|
|
1406
|
+
/** CX node id -> the id given to the sink, for the nodes whose original id was restored. */
|
|
1407
|
+
private readonly sinkIds = new Map<NodeId, NodeId>();
|
|
1408
|
+
|
|
1081
1409
|
/** The subnetwork ids of the document. */
|
|
1082
1410
|
private readonly subnetworks = new Set<NodeId>();
|
|
1083
1411
|
|
|
@@ -1148,6 +1476,7 @@ class CxReader {
|
|
|
1148
1476
|
this.direction.setHeader(true);
|
|
1149
1477
|
this.checkMembers();
|
|
1150
1478
|
this.checkRootOnly();
|
|
1479
|
+
this.checkTableColumns();
|
|
1151
1480
|
const nodeColumns = this.declareAttributes("node");
|
|
1152
1481
|
this.readNodes();
|
|
1153
1482
|
this.readGroups();
|
|
@@ -1160,9 +1489,14 @@ class CxReader {
|
|
|
1160
1489
|
this.readVisualProperties();
|
|
1161
1490
|
this.readProvenance();
|
|
1162
1491
|
for (const [kind, count] of this.dangling) {
|
|
1163
|
-
report.warning(
|
|
1164
|
-
|
|
1165
|
-
|
|
1492
|
+
report.warning(
|
|
1493
|
+
"validation-error",
|
|
1494
|
+
DANGLING_REFERENCE_CODE,
|
|
1495
|
+
`${count} ${kind}${plural(count)} ${agree(count, "names", "name")} nothing; ignored`,
|
|
1496
|
+
{
|
|
1497
|
+
element: kind,
|
|
1498
|
+
},
|
|
1499
|
+
);
|
|
1166
1500
|
}
|
|
1167
1501
|
this.setMeta();
|
|
1168
1502
|
}
|
|
@@ -1194,16 +1528,6 @@ class CxReader {
|
|
|
1194
1528
|
return this.plan.nodes === null || this.plan.nodes.has(id);
|
|
1195
1529
|
}
|
|
1196
1530
|
|
|
1197
|
-
/**
|
|
1198
|
-
* The key of the node and edge row maps for a CX id as an aspect writes it: the id the `ids`
|
|
1199
|
-
* option makes of it, which is what idOf() stored the element under.
|
|
1200
|
-
* @param raw - the CX id
|
|
1201
|
-
* @returns the row key
|
|
1202
|
-
*/
|
|
1203
|
-
private key(raw: NodeId): NodeId {
|
|
1204
|
-
return this.options.ids === "keep" ? raw : this.coercer.value(raw);
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
1531
|
/**
|
|
1208
1532
|
* Whether a scoped value applies to this graph: unscoped, or scoped to its subnetwork. A scope
|
|
1209
1533
|
* naming no subnetwork is counted as dangling.
|
|
@@ -1225,14 +1549,16 @@ class CxReader {
|
|
|
1225
1549
|
}
|
|
1226
1550
|
|
|
1227
1551
|
/**
|
|
1228
|
-
* The id of an element
|
|
1552
|
+
* The id of an element by the CX id rule, as the document's references name it (the `ids`
|
|
1553
|
+
* option applies only to the id the sink holds, sinkId()).
|
|
1229
1554
|
* @param raw - the parsed id
|
|
1230
1555
|
* @param inexact - whether the literal was not a plain integer
|
|
1231
1556
|
* @param element - the element name for issues
|
|
1557
|
+
* @param line - the element's line
|
|
1232
1558
|
* @param edgeId - whether this is an edge's own id (stored in the f64 id column, not kept as digits)
|
|
1233
1559
|
* @returns the id, or null when it was reported
|
|
1234
1560
|
*/
|
|
1235
|
-
private idOf(raw: unknown, inexact: boolean, element: string, edgeId = false): NodeId | null {
|
|
1561
|
+
private idOf(raw: unknown, inexact: boolean, element: string, line: number, edgeId = false): NodeId | null {
|
|
1236
1562
|
try {
|
|
1237
1563
|
const parsed = cxId(raw, inexact);
|
|
1238
1564
|
if (parsed.note === "text") {
|
|
@@ -1240,7 +1566,7 @@ class CxReader {
|
|
|
1240
1566
|
"coercion",
|
|
1241
1567
|
ID_TEXT_TYPE_CODE,
|
|
1242
1568
|
`${element}: the id ${shown(raw)} is not written as an integer; read as ${String(parsed.id)}`,
|
|
1243
|
-
{ element },
|
|
1569
|
+
{ line, element },
|
|
1244
1570
|
);
|
|
1245
1571
|
} else if (parsed.note === "precision" && edgeId) {
|
|
1246
1572
|
// the edge is told apart by its digits, but the id column is f64
|
|
@@ -1248,7 +1574,7 @@ class CxReader {
|
|
|
1248
1574
|
"precision",
|
|
1249
1575
|
PRECISION_CODE,
|
|
1250
1576
|
`${element}: the edge id ${String(parsed.id)} is beyond 2^53; the id column holds the nearest double`,
|
|
1251
|
-
{ element },
|
|
1577
|
+
{ line, element },
|
|
1252
1578
|
`${PRECISION_CODE}:edge`,
|
|
1253
1579
|
);
|
|
1254
1580
|
} else if (parsed.note === "precision") {
|
|
@@ -1257,31 +1583,101 @@ class CxReader {
|
|
|
1257
1583
|
"precision",
|
|
1258
1584
|
PRECISION_CODE,
|
|
1259
1585
|
`${element}: the id ${String(parsed.id)} is beyond 2^53; kept as its digits (a string id)`,
|
|
1260
|
-
{ element },
|
|
1586
|
+
{ line, element },
|
|
1261
1587
|
);
|
|
1262
1588
|
}
|
|
1263
|
-
return
|
|
1589
|
+
return parsed.id;
|
|
1264
1590
|
} catch (err) {
|
|
1265
|
-
this.report.recordError(err, { element });
|
|
1591
|
+
this.report.recordError(err, { line, element });
|
|
1266
1592
|
return null;
|
|
1267
1593
|
}
|
|
1268
1594
|
}
|
|
1269
1595
|
|
|
1596
|
+
/**
|
|
1597
|
+
* The id the sink holds a CX node under: its restored original id, else the `ids` option applied.
|
|
1598
|
+
* @param id - the CX id
|
|
1599
|
+
* @returns the sink id
|
|
1600
|
+
*/
|
|
1601
|
+
private sinkId(id: NodeId): NodeId {
|
|
1602
|
+
const original = this.sinkIds.get(id);
|
|
1603
|
+
if (original !== undefined) {
|
|
1604
|
+
return original;
|
|
1605
|
+
}
|
|
1606
|
+
return this.options.ids === "keep" ? id : this.coercer.value(id);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
/**
|
|
1610
|
+
* Add a node to the sink (once per sink id) and map its CX id to the row.
|
|
1611
|
+
* @param id - the CX id
|
|
1612
|
+
* @returns the row, and whether the sink id was new
|
|
1613
|
+
*/
|
|
1614
|
+
private addNode(id: NodeId): { readonly row: number; readonly added: boolean } {
|
|
1615
|
+
const sinkId = this.sinkId(id);
|
|
1616
|
+
const existing = this.sinkRows.get(sinkId);
|
|
1617
|
+
if (existing !== undefined) {
|
|
1618
|
+
this.nodeRows.set(id, existing);
|
|
1619
|
+
return { row: existing, added: false };
|
|
1620
|
+
}
|
|
1621
|
+
const row = this.sink.addNode(sinkId);
|
|
1622
|
+
this.sinkRows.set(sinkId, row);
|
|
1623
|
+
this.nodeRows.set(id, row);
|
|
1624
|
+
return { row, added: true };
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1270
1627
|
/**
|
|
1271
1628
|
* Record a long value beyond 2^53 (once).
|
|
1272
1629
|
* @param name - the attribute
|
|
1273
1630
|
* @param digits - the value
|
|
1274
1631
|
*/
|
|
1275
1632
|
private precision(name: string, digits: string): void {
|
|
1633
|
+
const overflow = !Number.isFinite(Number(digits));
|
|
1276
1634
|
this.report.warnOnce(
|
|
1277
1635
|
"precision",
|
|
1278
1636
|
PRECISION_CODE,
|
|
1279
|
-
|
|
1637
|
+
overflow
|
|
1638
|
+
? `"${name}": ${digits} is beyond the range of a double; stored as ${String(Number(digits))}`
|
|
1639
|
+
: `"${name}": ${digits} is beyond 2^53; stored as the nearest double`,
|
|
1280
1640
|
{ element: name },
|
|
1281
1641
|
`${PRECISION_CODE}:value`,
|
|
1282
1642
|
);
|
|
1283
1643
|
}
|
|
1284
1644
|
|
|
1645
|
+
/**
|
|
1646
|
+
* Report the keys of a node or edge element CX does not define, once per key.
|
|
1647
|
+
* @param record - the element
|
|
1648
|
+
* @param known - the keys it defines
|
|
1649
|
+
* @param what - nodes or edges
|
|
1650
|
+
* @param line - its line
|
|
1651
|
+
*/
|
|
1652
|
+
private unknownKeys(record: Record<string, unknown>, known: ReadonlySet<string>, what: string, line: number): void {
|
|
1653
|
+
for (const key of Object.keys(record)) {
|
|
1654
|
+
if (!known.has(key)) {
|
|
1655
|
+
this.report.warnOnce(
|
|
1656
|
+
"unsupported",
|
|
1657
|
+
UNKNOWN_ELEMENT_CODE,
|
|
1658
|
+
`the ${what} key "${key}" is not defined by CX; its values are not read`,
|
|
1659
|
+
{ line, element: key },
|
|
1660
|
+
`${UNKNOWN_ELEMENT_CODE}:${what}:${key}`,
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/** Report cyTableColumn entries for a table CX does not define (once per table name). */
|
|
1667
|
+
private checkTableColumns(): void {
|
|
1668
|
+
for (const { value, line } of aspect(this.doc, "cyTableColumn")) {
|
|
1669
|
+
if (isRecord(value) && typeof value.applies_to === "string" && !TABLES.has(value.applies_to)) {
|
|
1670
|
+
this.report.warnOnce(
|
|
1671
|
+
"unsupported",
|
|
1672
|
+
UNKNOWN_ELEMENT_CODE,
|
|
1673
|
+
`a cyTableColumn entry applies to "${value.applies_to}", which is not node_table, edge_table or network_table; ignored`,
|
|
1674
|
+
{ line, element: value.applies_to },
|
|
1675
|
+
`${UNKNOWN_ELEMENT_CODE}:table:${value.applies_to}`,
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1285
1681
|
/** Count the subnetwork members that name no node or edge. */
|
|
1286
1682
|
private checkMembers(): void {
|
|
1287
1683
|
for (const id of this.plan.nodes ?? []) {
|
|
@@ -1328,7 +1724,7 @@ class CxReader {
|
|
|
1328
1724
|
this.report.warning(
|
|
1329
1725
|
"unsupported",
|
|
1330
1726
|
CX_ISSUE.ROOT_ONLY,
|
|
1331
|
-
`${nodes} node(
|
|
1727
|
+
`${nodes} node${plural(nodes)} and ${edges} edge${plural(edges)} of the root network belong to no subnetwork (cySubNetworks); they are not read`,
|
|
1332
1728
|
{ element: "cySubNetworks" },
|
|
1333
1729
|
);
|
|
1334
1730
|
}
|
|
@@ -1359,7 +1755,10 @@ class CxReader {
|
|
|
1359
1755
|
const declared = this.tableColumns(domain === "node" ? "node_table" : "edge_table");
|
|
1360
1756
|
const names = new Set<string>([...declared.keys(), ...this.doc.attributes[domain].keys()]);
|
|
1361
1757
|
for (const name of names) {
|
|
1362
|
-
if (
|
|
1758
|
+
if (
|
|
1759
|
+
(domain === "edge" && name === this.options.weightFrom) ||
|
|
1760
|
+
(domain === "node" && name === ORIGINAL_ID_ATTRIBUTE && this.options.restoreMangledIds)
|
|
1761
|
+
) {
|
|
1363
1762
|
continue;
|
|
1364
1763
|
}
|
|
1365
1764
|
const types: CxType[] = [];
|
|
@@ -1455,7 +1854,15 @@ class CxReader {
|
|
|
1455
1854
|
continue;
|
|
1456
1855
|
}
|
|
1457
1856
|
const type = cxType(value.d);
|
|
1458
|
-
if (type
|
|
1857
|
+
if (type === null) {
|
|
1858
|
+
this.report.warnOnce(
|
|
1859
|
+
"unsupported",
|
|
1860
|
+
UNKNOWN_ATTR_TYPE_CODE,
|
|
1861
|
+
`cyTableColumn declares "${value.n}" with the data type ${shown(value.d)}, which CX does not define; its values are typed by their own d`,
|
|
1862
|
+
{ element: value.n },
|
|
1863
|
+
`${UNKNOWN_ATTR_TYPE_CODE}:table:${table}:${value.n}`,
|
|
1864
|
+
);
|
|
1865
|
+
} else {
|
|
1459
1866
|
out.set(value.n, type);
|
|
1460
1867
|
}
|
|
1461
1868
|
}
|
|
@@ -1515,14 +1922,6 @@ class CxReader {
|
|
|
1515
1922
|
if (scope === 0) {
|
|
1516
1923
|
continue;
|
|
1517
1924
|
}
|
|
1518
|
-
const target = refId(value.po);
|
|
1519
|
-
const row = target === null ? undefined : rows.get(this.key(target));
|
|
1520
|
-
if (row === undefined) {
|
|
1521
|
-
if (target === null || !root.has(target)) {
|
|
1522
|
-
this.dangle(`${domain} attribute target`);
|
|
1523
|
-
}
|
|
1524
|
-
continue;
|
|
1525
|
-
}
|
|
1526
1925
|
if (value.v === undefined) {
|
|
1527
1926
|
this.report.error(
|
|
1528
1927
|
"missing-value",
|
|
@@ -1535,26 +1934,37 @@ class CxReader {
|
|
|
1535
1934
|
);
|
|
1536
1935
|
continue;
|
|
1537
1936
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1937
|
+
// po names one element, or (cxio's shared values) a list of them
|
|
1938
|
+
for (const raw of Array.isArray(value.po) ? (value.po as unknown[]) : [value.po]) {
|
|
1939
|
+
const target = refId(raw);
|
|
1940
|
+
const row = target === null ? undefined : rows.get(target);
|
|
1941
|
+
if (row === undefined) {
|
|
1942
|
+
if (target === null || !root.has(target)) {
|
|
1943
|
+
this.dangle(`${domain} attribute target`);
|
|
1944
|
+
}
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
if (value.v === null) {
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
const previous = chosen.get(row);
|
|
1951
|
+
if (previous !== undefined && previous.scope > scope) {
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
if (
|
|
1955
|
+
previous?.scope === scope &&
|
|
1956
|
+
JSON.stringify(previous.value) !== JSON.stringify(plainJson(value.v))
|
|
1957
|
+
) {
|
|
1958
|
+
this.report.warnOnce(
|
|
1959
|
+
"validation-error",
|
|
1960
|
+
DUPLICATE_ATTRIBUTE_CODE,
|
|
1961
|
+
`${domain} ${shown(raw)} has the attribute "${column.name}" twice; the later value wins`,
|
|
1962
|
+
{ line, element: column.name },
|
|
1963
|
+
`${DUPLICATE_ATTRIBUTE_CODE}:${domain}:${column.name}`,
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
chosen.set(row, { scope, value: value.v, d: value.d, line });
|
|
1556
1967
|
}
|
|
1557
|
-
chosen.set(row, { scope, value: value.v, d: value.d, line });
|
|
1558
1968
|
}
|
|
1559
1969
|
for (const [row, { value, d, line }] of chosen) {
|
|
1560
1970
|
this.writeCell(domain, column, row, value, d, line);
|
|
@@ -1608,6 +2018,15 @@ class CxReader {
|
|
|
1608
2018
|
}
|
|
1609
2019
|
let { handle } = column;
|
|
1610
2020
|
if (this.isStructural(domain, column.name)) {
|
|
2021
|
+
if (Array.isArray(raw)) {
|
|
2022
|
+
this.report.error(
|
|
2023
|
+
"validation-error",
|
|
2024
|
+
BAD_VALUE_CODE,
|
|
2025
|
+
`${element}: ${shown(raw)} is a list; "${column.name}" holds one string, the cell is unset`,
|
|
2026
|
+
{ line, element: column.name },
|
|
2027
|
+
);
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
1611
2030
|
handle = this.coreColumn(domain, column.name);
|
|
1612
2031
|
const prior = this.coreValue(domain, row, column.name);
|
|
1613
2032
|
if (prior !== undefined && prior !== value) {
|
|
@@ -1684,7 +2103,8 @@ class CxReader {
|
|
|
1684
2103
|
|
|
1685
2104
|
/** Read the nodes of this graph. */
|
|
1686
2105
|
private readNodes(): void {
|
|
1687
|
-
const { report
|
|
2106
|
+
const { report } = this;
|
|
2107
|
+
const originals = this.originalIds();
|
|
1688
2108
|
for (const held of aspect(this.doc, "nodes")) {
|
|
1689
2109
|
this.checkAbort();
|
|
1690
2110
|
const { value, line } = held;
|
|
@@ -1706,40 +2126,66 @@ class CxReader {
|
|
|
1706
2126
|
report.counts.skippedNodes++;
|
|
1707
2127
|
continue;
|
|
1708
2128
|
}
|
|
2129
|
+
this.unknownKeys(value, NODE_KEYS, "node", line);
|
|
1709
2130
|
const element = `node ${shown(value["@id"])}`;
|
|
1710
|
-
const id = this.idOf(value["@id"], (held.inexact & 1) !== 0, element);
|
|
2131
|
+
const id = this.idOf(value["@id"], (held.inexact & 1) !== 0, element, line);
|
|
1711
2132
|
if (id === null) {
|
|
1712
2133
|
report.counts.skippedNodes++;
|
|
1713
2134
|
continue;
|
|
1714
2135
|
}
|
|
1715
|
-
|
|
1716
|
-
if (!this.inGraph(refId(value["@id"]) ?? id)) {
|
|
2136
|
+
if (!this.inGraph(id)) {
|
|
1717
2137
|
continue;
|
|
1718
2138
|
}
|
|
1719
2139
|
let row = this.nodeRows.get(id);
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
} else {
|
|
2140
|
+
let added = false;
|
|
2141
|
+
if (row === undefined) {
|
|
2142
|
+
const original = originals.get(refId(value["@id"]) ?? id);
|
|
2143
|
+
if (original !== undefined) {
|
|
2144
|
+
this.idType = "mixed";
|
|
2145
|
+
this.sinkIds.set(id, original);
|
|
2146
|
+
}
|
|
1728
2147
|
try {
|
|
1729
|
-
row =
|
|
2148
|
+
({ row, added } = this.addNode(id));
|
|
1730
2149
|
} catch (err) {
|
|
1731
2150
|
report.recordError(err, { line, element });
|
|
1732
2151
|
report.counts.skippedNodes++;
|
|
1733
2152
|
continue;
|
|
1734
2153
|
}
|
|
1735
|
-
|
|
2154
|
+
}
|
|
2155
|
+
if (added) {
|
|
1736
2156
|
report.counts.nodes++;
|
|
2157
|
+
} else {
|
|
2158
|
+
report.warning(
|
|
2159
|
+
"merged",
|
|
2160
|
+
DUPLICATE_NODE_CODE,
|
|
2161
|
+
`${element} is declared more than once; its fields are merged (the later values win)`,
|
|
2162
|
+
{ line, element },
|
|
2163
|
+
);
|
|
1737
2164
|
}
|
|
1738
2165
|
this.writeCore("node", row, "name", value.n, element, line);
|
|
1739
2166
|
this.writeCore("node", row, "represents", value.r, element, line);
|
|
1740
2167
|
}
|
|
1741
2168
|
}
|
|
1742
2169
|
|
|
2170
|
+
/**
|
|
2171
|
+
* The original ids the exporter's `sanitizeIds: "mangle"` kept in the `graphty:originalId`
|
|
2172
|
+
* attribute, when they are to be restored.
|
|
2173
|
+
* @returns CX node id -> original id
|
|
2174
|
+
*/
|
|
2175
|
+
private originalIds(): Map<NodeId, string> {
|
|
2176
|
+
const out = new Map<NodeId, string>();
|
|
2177
|
+
if (!this.options.restoreMangledIds) {
|
|
2178
|
+
return out;
|
|
2179
|
+
}
|
|
2180
|
+
for (const { value } of this.doc.attributes.node.get(ORIGINAL_ID_ATTRIBUTE) ?? []) {
|
|
2181
|
+
const po = isRecord(value) && this.scopePeek(value.s) !== 0 ? refId(value.po) : null;
|
|
2182
|
+
if (po !== null && isRecord(value) && typeof value.v === "string") {
|
|
2183
|
+
out.set(po, value.v);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
return out;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
1743
2189
|
/**
|
|
1744
2190
|
* Write a core string field (n, r, i).
|
|
1745
2191
|
* @param domain - node or edge
|
|
@@ -1799,30 +2245,40 @@ class CxReader {
|
|
|
1799
2245
|
}
|
|
1800
2246
|
const parents = new Map<number, number[]>();
|
|
1801
2247
|
const collapsed: [number, boolean][] = [];
|
|
1802
|
-
for (const
|
|
1803
|
-
|
|
2248
|
+
for (const held of groups) {
|
|
2249
|
+
const group = structuralId(held, "cyGroups", "@id", this.report);
|
|
2250
|
+
if (group === null) {
|
|
1804
2251
|
continue;
|
|
1805
2252
|
}
|
|
1806
|
-
const id =
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
2253
|
+
const { value, id } = group;
|
|
2254
|
+
const { line } = held;
|
|
2255
|
+
if (value.nodes !== undefined && value.nodes !== null && !Array.isArray(value.nodes)) {
|
|
2256
|
+
this.report.error(
|
|
2257
|
+
"validation-error",
|
|
2258
|
+
BAD_VALUE_CODE,
|
|
2259
|
+
`group ${String(id)}: nodes is ${shown(value.nodes)}, not a list of node ids; no member is read`,
|
|
2260
|
+
{ line, element: String(id) },
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
if (value.collapsed !== undefined && value.collapsed !== null && typeof value.collapsed !== "boolean") {
|
|
2264
|
+
this.report.error(
|
|
2265
|
+
"validation-error",
|
|
2266
|
+
BAD_VALUE_CODE,
|
|
2267
|
+
`group ${String(id)}: collapsed is ${shown(value.collapsed)}, not a boolean; unset`,
|
|
2268
|
+
{ line, element: String(id) },
|
|
2269
|
+
);
|
|
1813
2270
|
}
|
|
1814
|
-
let groupRow = this.nodeRows.get(
|
|
2271
|
+
let groupRow = this.nodeRows.get(id);
|
|
1815
2272
|
if (groupRow === undefined) {
|
|
1816
2273
|
if (this.rootNodes.has(id) || !this.hasMemberIn(value.nodes)) {
|
|
1817
2274
|
continue;
|
|
1818
2275
|
}
|
|
1819
2276
|
try {
|
|
1820
|
-
groupRow = this.
|
|
2277
|
+
groupRow = this.addNode(id).row;
|
|
1821
2278
|
} catch (err) {
|
|
1822
2279
|
this.report.recordError(err, { line, element: String(id) });
|
|
1823
2280
|
continue;
|
|
1824
2281
|
}
|
|
1825
|
-
this.nodeRows.set(this.key(id), groupRow);
|
|
1826
2282
|
this.report.counts.nodes++;
|
|
1827
2283
|
this.report.warning(
|
|
1828
2284
|
"coercion",
|
|
@@ -1839,7 +2295,7 @@ class CxReader {
|
|
|
1839
2295
|
}
|
|
1840
2296
|
for (const raw of Array.isArray(value.nodes) ? (value.nodes as unknown[]) : []) {
|
|
1841
2297
|
const member = refId(raw);
|
|
1842
|
-
const row = member === null ? undefined : this.nodeRows.get(
|
|
2298
|
+
const row = member === null ? undefined : this.nodeRows.get(member);
|
|
1843
2299
|
if (row === undefined) {
|
|
1844
2300
|
if (member === null || !this.rootNodes.has(member)) {
|
|
1845
2301
|
this.report.error(
|
|
@@ -1896,7 +2352,7 @@ class CxReader {
|
|
|
1896
2352
|
Array.isArray(raw) &&
|
|
1897
2353
|
raw.some((item) => {
|
|
1898
2354
|
const id = refId(item);
|
|
1899
|
-
return id !== null && this.nodeRows.has(
|
|
2355
|
+
return id !== null && this.nodeRows.has(id);
|
|
1900
2356
|
})
|
|
1901
2357
|
);
|
|
1902
2358
|
}
|
|
@@ -1963,6 +2419,12 @@ class CxReader {
|
|
|
1963
2419
|
for (const { value, line } of entries) {
|
|
1964
2420
|
this.checkAbort();
|
|
1965
2421
|
if (!isRecord(value)) {
|
|
2422
|
+
this.report.error(
|
|
2423
|
+
"parse-error",
|
|
2424
|
+
BAD_ASPECT_BLOCK_CODE,
|
|
2425
|
+
`a cartesianLayout element is ${shown(value)}, not an object; skipped`,
|
|
2426
|
+
{ line, element: "cartesianLayout" },
|
|
2427
|
+
);
|
|
1966
2428
|
continue;
|
|
1967
2429
|
}
|
|
1968
2430
|
const view = value.view === undefined || value.view === null ? null : refId(value.view);
|
|
@@ -1983,14 +2445,31 @@ class CxReader {
|
|
|
1983
2445
|
);
|
|
1984
2446
|
continue;
|
|
1985
2447
|
}
|
|
2448
|
+
if (!fitsF32(value.x) || !fitsF32(value.y)) {
|
|
2449
|
+
this.report.error(
|
|
2450
|
+
"validation-error",
|
|
2451
|
+
BAD_VALUE_CODE,
|
|
2452
|
+
`a cartesianLayout element for node ${shown(value.node)} has the coordinates ${String(value.x)}, ${String(value.y)}, beyond what the f32 position column holds; skipped`,
|
|
2453
|
+
{ line, element: "cartesianLayout" },
|
|
2454
|
+
);
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
1986
2457
|
const node = refId(value.node);
|
|
1987
|
-
const row = node === null ? undefined : this.nodeRows.get(
|
|
2458
|
+
const row = node === null ? undefined : this.nodeRows.get(node);
|
|
1988
2459
|
if (row === undefined) {
|
|
1989
2460
|
if (node === null || !this.rootNodes.has(node)) {
|
|
1990
2461
|
this.dangle("layout entry");
|
|
1991
2462
|
}
|
|
1992
2463
|
continue;
|
|
1993
2464
|
}
|
|
2465
|
+
if (value.z !== undefined && value.z !== null && typeof value.z !== "number") {
|
|
2466
|
+
this.report.error(
|
|
2467
|
+
"validation-error",
|
|
2468
|
+
BAD_VALUE_CODE,
|
|
2469
|
+
`a cartesianLayout element for node ${shown(value.node)} has the z ${shown(value.z)}, not a number; z is unset`,
|
|
2470
|
+
{ line, element: "cartesianLayout" },
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
1994
2473
|
const key = `${slot}:${row}`;
|
|
1995
2474
|
if (seen.has(key)) {
|
|
1996
2475
|
this.report.warnOnce(
|
|
@@ -2069,6 +2548,7 @@ class CxReader {
|
|
|
2069
2548
|
private readEdges(): void {
|
|
2070
2549
|
const { report, sink } = this;
|
|
2071
2550
|
const members = this.plan.edges;
|
|
2551
|
+
this.weights = this.edgeWeights();
|
|
2072
2552
|
for (const held of aspect(this.doc, "edges")) {
|
|
2073
2553
|
this.checkAbort();
|
|
2074
2554
|
const { value, line } = held;
|
|
@@ -2090,13 +2570,14 @@ class CxReader {
|
|
|
2090
2570
|
report.counts.skippedEdges++;
|
|
2091
2571
|
continue;
|
|
2092
2572
|
}
|
|
2573
|
+
this.unknownKeys(value, EDGE_KEYS, "edge", line);
|
|
2093
2574
|
const element = `edge ${shown(value["@id"])}`;
|
|
2094
|
-
const id = this.idOf(value["@id"], (held.inexact & 1) !== 0, element, true);
|
|
2575
|
+
const id = this.idOf(value["@id"], (held.inexact & 1) !== 0, element, line, true);
|
|
2095
2576
|
if (id === null) {
|
|
2096
2577
|
report.counts.skippedEdges++;
|
|
2097
2578
|
continue;
|
|
2098
2579
|
}
|
|
2099
|
-
if (members !== null && !members.has(
|
|
2580
|
+
if (members !== null && !members.has(id)) {
|
|
2100
2581
|
continue;
|
|
2101
2582
|
}
|
|
2102
2583
|
if (value.s === undefined || value.s === null || value.t === undefined || value.t === null) {
|
|
@@ -2112,8 +2593,8 @@ class CxReader {
|
|
|
2112
2593
|
report.counts.skippedEdges++;
|
|
2113
2594
|
continue;
|
|
2114
2595
|
}
|
|
2115
|
-
const s = this.idOf(value.s, (held.inexact & 2) !== 0, element);
|
|
2116
|
-
const t = s === null ? null : this.idOf(value.t, (held.inexact & 4) !== 0, element);
|
|
2596
|
+
const s = this.idOf(value.s, (held.inexact & 2) !== 0, element, line);
|
|
2597
|
+
const t = s === null ? null : this.idOf(value.t, (held.inexact & 4) !== 0, element, line);
|
|
2117
2598
|
if (s === null || t === null) {
|
|
2118
2599
|
report.counts.skippedEdges++;
|
|
2119
2600
|
continue;
|
|
@@ -2143,7 +2624,7 @@ class CxReader {
|
|
|
2143
2624
|
const before = sink.edgeCount;
|
|
2144
2625
|
let edge: number;
|
|
2145
2626
|
try {
|
|
2146
|
-
edge = this.direction.addEdge(s, t, "directed", weight, { line, element });
|
|
2627
|
+
edge = this.direction.addEdge(this.sinkId(s), this.sinkId(t), "directed", weight, { line, element });
|
|
2147
2628
|
} catch (err) {
|
|
2148
2629
|
report.recordError(err, { line, element });
|
|
2149
2630
|
report.counts.skippedEdges++;
|
|
@@ -2186,39 +2667,89 @@ class CxReader {
|
|
|
2186
2667
|
return false;
|
|
2187
2668
|
}
|
|
2188
2669
|
try {
|
|
2189
|
-
|
|
2670
|
+
if (this.addNode(id).added) {
|
|
2671
|
+
this.report.counts.nodes++;
|
|
2672
|
+
}
|
|
2190
2673
|
} catch (err) {
|
|
2191
2674
|
this.report.recordError(err, { line, element });
|
|
2192
2675
|
return false;
|
|
2193
2676
|
}
|
|
2194
|
-
this.report.counts.nodes++;
|
|
2195
2677
|
return true;
|
|
2196
2678
|
}
|
|
2197
2679
|
|
|
2198
|
-
/** The weight attribute values of the edges, by edge id
|
|
2199
|
-
private weights
|
|
2680
|
+
/** The weight attribute values of the edges, by edge id. */
|
|
2681
|
+
private weights = new Map<NodeId, unknown>();
|
|
2200
2682
|
|
|
2201
2683
|
/**
|
|
2202
|
-
* The
|
|
2203
|
-
*
|
|
2204
|
-
*
|
|
2684
|
+
* The weightFrom attribute values by edge id: this subnetwork's own value beats the unscoped
|
|
2685
|
+
* one, and of two in one scope the later wins (W_DUPLICATE_ATTRIBUTE when they differ), as
|
|
2686
|
+
* writeAttributes() rules every other attribute.
|
|
2687
|
+
* @returns the values (parsed JSON) by edge id
|
|
2205
2688
|
*/
|
|
2206
|
-
private
|
|
2689
|
+
private edgeWeights(): Map<NodeId, unknown> {
|
|
2207
2690
|
const { weightFrom } = this.options;
|
|
2691
|
+
const chosen = new Map<NodeId, { scope: number; value: unknown }>();
|
|
2208
2692
|
if (weightFrom === null) {
|
|
2209
|
-
return
|
|
2693
|
+
return new Map();
|
|
2210
2694
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
}
|
|
2695
|
+
for (const { value, line } of this.doc.attributes.edge.get(weightFrom) ?? []) {
|
|
2696
|
+
const scope = isRecord(value) ? this.scopePeek(value.s) : 0;
|
|
2697
|
+
if (!isRecord(value) || scope === 0 || value.v === undefined || value.v === null) {
|
|
2698
|
+
continue;
|
|
2699
|
+
}
|
|
2700
|
+
const v = plainJson(value.v);
|
|
2701
|
+
for (const raw of Array.isArray(value.po) ? (value.po as unknown[]) : [value.po]) {
|
|
2702
|
+
this.chooseWeight(chosen, raw, { scope, value: v }, line, weightFrom);
|
|
2220
2703
|
}
|
|
2221
2704
|
}
|
|
2705
|
+
return new Map([...chosen].map(([id, { value }]) => [id, value]));
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
/**
|
|
2709
|
+
* Keep one weight attribute element's value for one edge, unless a more specific scope already
|
|
2710
|
+
* gave it one; a second value in the same scope wins, with a warning.
|
|
2711
|
+
* @param chosen - the value chosen so far per edge
|
|
2712
|
+
* @param raw - the edge reference as written
|
|
2713
|
+
* @param weight - the value and the scope it applies in
|
|
2714
|
+
* @param weight.scope - the scope: higher is more specific
|
|
2715
|
+
* @param weight.value - the value
|
|
2716
|
+
* @param line - the element's line
|
|
2717
|
+
* @param weightFrom - the attribute name
|
|
2718
|
+
*/
|
|
2719
|
+
private chooseWeight(
|
|
2720
|
+
chosen: Map<NodeId, { scope: number; value: unknown }>,
|
|
2721
|
+
raw: unknown,
|
|
2722
|
+
weight: { scope: number; value: unknown },
|
|
2723
|
+
line: number,
|
|
2724
|
+
weightFrom: string,
|
|
2725
|
+
): void {
|
|
2726
|
+
const po = refId(raw);
|
|
2727
|
+
const previous = po === null ? undefined : chosen.get(po);
|
|
2728
|
+
if (po === null || (previous !== undefined && previous.scope > weight.scope)) {
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
if (previous?.scope === weight.scope && JSON.stringify(previous.value) !== JSON.stringify(weight.value)) {
|
|
2732
|
+
this.report.warnOnce(
|
|
2733
|
+
"validation-error",
|
|
2734
|
+
DUPLICATE_ATTRIBUTE_CODE,
|
|
2735
|
+
`edge ${shown(raw)} has the attribute "${weightFrom}" twice; the later value wins`,
|
|
2736
|
+
{ line, element: weightFrom },
|
|
2737
|
+
`${DUPLICATE_ATTRIBUTE_CODE}:edge:${weightFrom}`,
|
|
2738
|
+
);
|
|
2739
|
+
}
|
|
2740
|
+
chosen.set(po, weight);
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
/**
|
|
2744
|
+
* The weight of an edge: its weightFrom attribute, parsed as a number (the declared d is not
|
|
2745
|
+
* consulted: a value that is not a number is E_INVALID_WEIGHT whatever its type says).
|
|
2746
|
+
* @param id - the edge id
|
|
2747
|
+
* @returns the weight, or undefined; E_INVALID_WEIGHT for a non-number
|
|
2748
|
+
*/
|
|
2749
|
+
private weightOf(id: NodeId): number | undefined {
|
|
2750
|
+
if (this.options.weightFrom === null) {
|
|
2751
|
+
return undefined;
|
|
2752
|
+
}
|
|
2222
2753
|
const raw = this.weights.get(id);
|
|
2223
2754
|
if (typeof raw === "string" && isNullText(raw)) {
|
|
2224
2755
|
return undefined;
|
|
@@ -2233,17 +2764,42 @@ class CxReader {
|
|
|
2233
2764
|
for (const [name, elements] of this.doc.attributes.network) {
|
|
2234
2765
|
let chosen: Record<string, unknown> | null = null;
|
|
2235
2766
|
let chosenScope = 0;
|
|
2236
|
-
for (const { value } of elements) {
|
|
2767
|
+
for (const { value, line } of elements) {
|
|
2237
2768
|
if (!isRecord(value)) {
|
|
2238
2769
|
continue;
|
|
2239
2770
|
}
|
|
2240
2771
|
const scope = this.scopeOf(value.s);
|
|
2241
|
-
if (scope
|
|
2772
|
+
if (scope === 0) {
|
|
2773
|
+
continue;
|
|
2774
|
+
}
|
|
2775
|
+
if (value.v === undefined) {
|
|
2776
|
+
this.report.error(
|
|
2777
|
+
"missing-value",
|
|
2778
|
+
BAD_VALUE_CODE,
|
|
2779
|
+
`a networkAttributes element for "${name}" has no v; skipped`,
|
|
2780
|
+
{ line, element: name },
|
|
2781
|
+
);
|
|
2782
|
+
continue;
|
|
2783
|
+
}
|
|
2784
|
+
if (
|
|
2785
|
+
chosen !== null &&
|
|
2786
|
+
scope === chosenScope &&
|
|
2787
|
+
JSON.stringify(plainJson(chosen.v)) !== JSON.stringify(plainJson(value.v))
|
|
2788
|
+
) {
|
|
2789
|
+
this.report.warnOnce(
|
|
2790
|
+
"validation-error",
|
|
2791
|
+
DUPLICATE_ATTRIBUTE_CODE,
|
|
2792
|
+
`the network attribute "${name}" is given twice; the later value wins`,
|
|
2793
|
+
{ line, element: name },
|
|
2794
|
+
`${DUPLICATE_ATTRIBUTE_CODE}:network:${name}`,
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
if (scope >= chosenScope) {
|
|
2242
2798
|
chosen = value;
|
|
2243
2799
|
chosenScope = scope;
|
|
2244
2800
|
}
|
|
2245
2801
|
}
|
|
2246
|
-
if (chosen
|
|
2802
|
+
if (chosen?.v === undefined || chosen.v === null) {
|
|
2247
2803
|
continue;
|
|
2248
2804
|
}
|
|
2249
2805
|
const type = cxType(chosen.d);
|
|
@@ -2321,7 +2877,7 @@ class CxReader {
|
|
|
2321
2877
|
const domain = value.properties_of === "nodes" ? "node" : "edge";
|
|
2322
2878
|
const target = refId(value.applies_to);
|
|
2323
2879
|
const rows = domain === "node" ? this.nodeRows : this.edgeRows;
|
|
2324
|
-
const row = target === null ? undefined : rows.get(
|
|
2880
|
+
const row = target === null ? undefined : rows.get(target);
|
|
2325
2881
|
if (row === undefined) {
|
|
2326
2882
|
if (target === null || !(domain === "node" ? this.rootNodes : this.rootEdges).has(target)) {
|
|
2327
2883
|
this.dangle(`${domain} visual property entry`);
|
|
@@ -2354,7 +2910,7 @@ class CxReader {
|
|
|
2354
2910
|
name: property,
|
|
2355
2911
|
dtype: "string",
|
|
2356
2912
|
nullable: true,
|
|
2357
|
-
origin: { format: CX_FORMAT, id:
|
|
2913
|
+
origin: { format: CX_FORMAT, id: property, namespace: CX_BYPASS_NAMESPACE },
|
|
2358
2914
|
},
|
|
2359
2915
|
this.report,
|
|
2360
2916
|
);
|
|
@@ -2379,15 +2935,15 @@ class CxReader {
|
|
|
2379
2935
|
}
|
|
2380
2936
|
if (defaults + mappings + dependencies + tableStyles > 0) {
|
|
2381
2937
|
const parts = [
|
|
2382
|
-
`cyVisualProperties: ${defaults} default(
|
|
2938
|
+
`cyVisualProperties: ${defaults} default${plural(defaults)}, ${mappings} mapping${plural(mappings)}, ${dependencies} dependenc(ies)`,
|
|
2383
2939
|
];
|
|
2384
2940
|
if (tableStyles > 0) {
|
|
2385
|
-
parts.push(`${tableStyles} table style element(
|
|
2941
|
+
parts.push(`${tableStyles} table style element${plural(tableStyles)}`);
|
|
2386
2942
|
}
|
|
2387
2943
|
this.report.warning(
|
|
2388
2944
|
"unsupported",
|
|
2389
2945
|
STYLES_NOT_IMPORTED_CODE,
|
|
2390
|
-
`the file's style rules are not applied (${parts.join("; ")}); they are kept in meta.extra.cx
|
|
2946
|
+
`the file's style rules are not applied (${parts.join("; ")}); they are kept in meta.extra.cx`,
|
|
2391
2947
|
{ element: "cyVisualProperties" },
|
|
2392
2948
|
);
|
|
2393
2949
|
}
|
|
@@ -2416,7 +2972,7 @@ class CxReader {
|
|
|
2416
2972
|
continue;
|
|
2417
2973
|
}
|
|
2418
2974
|
const po = refId(value.po);
|
|
2419
|
-
const row = po === null ? undefined : this.nodeRows.get(
|
|
2975
|
+
const row = po === null ? undefined : this.nodeRows.get(po);
|
|
2420
2976
|
if (row === undefined) {
|
|
2421
2977
|
if (po === null || !this.rootNodes.has(po)) {
|
|
2422
2978
|
this.dangle("functionTerms entry");
|
|
@@ -2434,8 +2990,8 @@ class CxReader {
|
|
|
2434
2990
|
}
|
|
2435
2991
|
const node = refId(value.node);
|
|
2436
2992
|
const edge = refId(value.edge);
|
|
2437
|
-
const row = node === null ? undefined : this.nodeRows.get(
|
|
2438
|
-
const edgeRow = edge === null ? undefined : this.edgeRows.get(
|
|
2993
|
+
const row = node === null ? undefined : this.nodeRows.get(node);
|
|
2994
|
+
const edgeRow = edge === null ? undefined : this.edgeRows.get(edge);
|
|
2439
2995
|
if (row === undefined || edgeRow === undefined) {
|
|
2440
2996
|
if (node === null || !this.rootNodes.has(node) || edge === null || !this.rootEdges.has(edge)) {
|
|
2441
2997
|
this.dangle("reifiedEdges entry");
|
|
@@ -2474,16 +3030,35 @@ class CxReader {
|
|
|
2474
3030
|
return;
|
|
2475
3031
|
}
|
|
2476
3032
|
const fields = [...new Set(records.flatMap((r) => Object.keys(r)).filter((k) => k !== "@id"))];
|
|
3033
|
+
const names = new Set(["id"]);
|
|
2477
3034
|
const decls: ColumnDecl[] = [
|
|
2478
3035
|
{ name: "id", dtype: "f64", nullable: true },
|
|
2479
|
-
...fields.map((field): ColumnDecl =>
|
|
3036
|
+
...fields.map((field): ColumnDecl => {
|
|
3037
|
+
const column = uniqueColumnName(field, null, (n) => names.has(n));
|
|
3038
|
+
names.add(column);
|
|
3039
|
+
if (column !== field) {
|
|
3040
|
+
this.report.warning(
|
|
3041
|
+
"coercion",
|
|
3042
|
+
COLUMN_RENAMED_CODE,
|
|
3043
|
+
`${name} column "${field}" renamed to "${column}": the table's own id column holds that name`,
|
|
3044
|
+
{ element: field },
|
|
3045
|
+
);
|
|
3046
|
+
}
|
|
3047
|
+
return { name: column, dtype: "json", nullable: true };
|
|
3048
|
+
}),
|
|
2480
3049
|
];
|
|
2481
3050
|
const table = this.sink.addExtensionTable(name, decls);
|
|
3051
|
+
const precision = (digits: string): void => {
|
|
3052
|
+
this.precision(name, digits);
|
|
3053
|
+
};
|
|
2482
3054
|
for (const record of records) {
|
|
2483
3055
|
const id = refId(record["@id"]);
|
|
3056
|
+
if (typeof id === "string") {
|
|
3057
|
+
precision(id);
|
|
3058
|
+
}
|
|
2484
3059
|
this.sink.addExtensionRow(table, [
|
|
2485
3060
|
id === null ? null : Number(id),
|
|
2486
|
-
...fields.map((field) => (record[field] === undefined ? null : plainJson(record[field]))),
|
|
3061
|
+
...fields.map((field) => (record[field] === undefined ? null : plainJson(record[field], precision))),
|
|
2487
3062
|
]);
|
|
2488
3063
|
}
|
|
2489
3064
|
}
|
|
@@ -2509,7 +3084,7 @@ class CxReader {
|
|
|
2509
3084
|
.map(Number);
|
|
2510
3085
|
for (const raw of targets) {
|
|
2511
3086
|
const target = refId(raw);
|
|
2512
|
-
const row = target === null ? undefined : rows.get(
|
|
3087
|
+
const row = target === null ? undefined : rows.get(target);
|
|
2513
3088
|
if (row === undefined) {
|
|
2514
3089
|
if (target === null || !root.has(target)) {
|
|
2515
3090
|
this.dangle(`${aspectName} entry`);
|
|
@@ -2576,12 +3151,24 @@ class CxReader {
|
|
|
2576
3151
|
};
|
|
2577
3152
|
const extra: Record<string, unknown> = Object.fromEntries(this.doc.kept);
|
|
2578
3153
|
const groups = aspect(this.doc, "cyGroups").map((h) => plainJson(h.value));
|
|
3154
|
+
const own: Record<string, unknown> = {};
|
|
2579
3155
|
if (groups.length > 0) {
|
|
2580
|
-
|
|
3156
|
+
own.groups = groups;
|
|
2581
3157
|
}
|
|
2582
3158
|
if (this.plan.subnetwork !== null) {
|
|
2583
|
-
|
|
3159
|
+
own.subnetwork = this.plan.subnetwork;
|
|
3160
|
+
}
|
|
3161
|
+
for (const key of OWN_EXTRA_KEYS) {
|
|
3162
|
+
if (key in own && this.doc.kept.has(key)) {
|
|
3163
|
+
this.report.warning(
|
|
3164
|
+
"unsupported",
|
|
3165
|
+
UNKNOWN_ELEMENT_CODE,
|
|
3166
|
+
`the aspect "${key}" is not kept: meta.extra.cx.${key} holds the importer's own ${key === "groups" ? "cyGroups" : "subnetwork"} data`,
|
|
3167
|
+
{ element: key },
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
2584
3170
|
}
|
|
3171
|
+
Object.assign(extra, own);
|
|
2585
3172
|
const { weightFrom } = this.options;
|
|
2586
3173
|
const name = this.plan.name ?? describe("name");
|
|
2587
3174
|
const description = describe("description");
|
|
@@ -2621,7 +3208,7 @@ async function run(
|
|
|
2621
3208
|
const first = new ImportReportBuilder(CX_FORMAT, resolved.errorLimit);
|
|
2622
3209
|
reportUnusedOptions(options, first, USED_OPTIONS);
|
|
2623
3210
|
const doc = await readDocument(input, first, resolved);
|
|
2624
|
-
const plans = graphPlans(doc);
|
|
3211
|
+
const plans = graphPlans(doc, first);
|
|
2625
3212
|
const chosen = pick(plans, first);
|
|
2626
3213
|
const reports: ImportReport[] = [];
|
|
2627
3214
|
chosen.forEach((index, k) => {
|
|
@@ -2636,10 +3223,12 @@ async function run(
|
|
|
2636
3223
|
}
|
|
2637
3224
|
|
|
2638
3225
|
/**
|
|
2639
|
-
* The CX version 1 importer plugin
|
|
3226
|
+
* The CX version 1 importer plugin.
|
|
3227
|
+
* @category Built-in formats
|
|
2640
3228
|
*/
|
|
2641
3229
|
export const cxImporter: GraphImporter<CxImportOptions> = Object.freeze({
|
|
2642
3230
|
format: CX_FORMAT,
|
|
3231
|
+
options: Object.freeze(["zAs"]),
|
|
2643
3232
|
extensions: Object.freeze([".cx"]),
|
|
2644
3233
|
mimeTypes: Object.freeze(["application/json"]),
|
|
2645
3234
|
|
|
@@ -2651,8 +3240,7 @@ export const cxImporter: GraphImporter<CxImportOptions> = Object.freeze({
|
|
|
2651
3240
|
* @returns the confidence
|
|
2652
3241
|
*/
|
|
2653
3242
|
sniff(head: Uint8Array): number {
|
|
2654
|
-
const
|
|
2655
|
-
const match = /^\s*\[\s*\{\s*"((?:[^"\\]|\\.)*)"\s*:/.exec(text);
|
|
3243
|
+
const match = /^\s*\[\s*\{\s*"((?:[^"\\]|\\.)*)"\s*:/.exec(headText(head));
|
|
2656
3244
|
if (match === null) {
|
|
2657
3245
|
return 0;
|
|
2658
3246
|
}
|
|
@@ -2685,11 +3273,11 @@ export const cxImporter: GraphImporter<CxImportOptions> = Object.freeze({
|
|
|
2685
3273
|
options,
|
|
2686
3274
|
first,
|
|
2687
3275
|
);
|
|
2688
|
-
if (plans.length > 1) {
|
|
3276
|
+
if (plans.length > 1 && !graphChosen(options)) {
|
|
2689
3277
|
first.warning(
|
|
2690
3278
|
"unsupported",
|
|
2691
3279
|
MULTIPLE_GRAPHS_CODE,
|
|
2692
|
-
`the collection holds ${plans.length} subnetworks; graph ${index} is read and ${plans.length - 1} skipped (
|
|
3280
|
+
`the collection holds ${plans.length} subnetworks; graph ${index} is read and ${plans.length - 1} skipped (importAllGraphs() reads every one)`,
|
|
2693
3281
|
);
|
|
2694
3282
|
}
|
|
2695
3283
|
return [index];
|
|
@@ -2729,7 +3317,7 @@ export const cxImporter: GraphImporter<CxImportOptions> = Object.freeze({
|
|
|
2729
3317
|
const doc = await readDocument(input, report, resolved);
|
|
2730
3318
|
const nodes = aspect(doc, "nodes").length;
|
|
2731
3319
|
const edges = aspect(doc, "edges").length;
|
|
2732
|
-
return graphPlans(doc).map((plan) =>
|
|
3320
|
+
return graphPlans(doc, report).map((plan) =>
|
|
2733
3321
|
Object.freeze({
|
|
2734
3322
|
index: plan.index,
|
|
2735
3323
|
name: plan.name,
|