@graphty/graph-io 0.3.25 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -414
- package/dist/chunks/children-CL3Cy0ez.js.map +1 -1
- package/dist/chunks/choose-BbJI55dx.js +56 -0
- package/dist/chunks/choose-BbJI55dx.js.map +1 -0
- package/dist/chunks/constants-BCsFsMj4.js +231 -0
- package/dist/chunks/constants-BCsFsMj4.js.map +1 -0
- package/dist/chunks/{escape-B-tkk_Cl.js → escape-D0g6NNUy.js} +382 -92
- package/dist/chunks/escape-D0g6NNUy.js.map +1 -0
- package/dist/chunks/export-BTI4yQgy.js +4333 -0
- package/dist/chunks/export-BTI4yQgy.js.map +1 -0
- package/dist/chunks/exporter-BMqk5oxW.js +3465 -0
- package/dist/chunks/exporter-BMqk5oxW.js.map +1 -0
- package/dist/chunks/{importer-eUREhBGj.js → exporter-Cra2GYcN.js} +4969 -3201
- package/dist/chunks/exporter-Cra2GYcN.js.map +1 -0
- package/dist/chunks/{exporter-BGrsWamJ.js → exporter-Dxh231ZF.js} +38 -229
- package/dist/chunks/exporter-Dxh231ZF.js.map +1 -0
- package/dist/chunks/{exporter-DIeGJXAZ.js → exporter-tne475KK.js} +22 -20
- package/dist/chunks/exporter-tne475KK.js.map +1 -0
- package/dist/chunks/importer-90YWhJoF.js +2811 -0
- package/dist/chunks/importer-90YWhJoF.js.map +1 -0
- package/dist/chunks/{importer-D_e7e7LX.js → importer-Bcl1d4xH.js} +398 -145
- package/dist/chunks/importer-Bcl1d4xH.js.map +1 -0
- package/dist/chunks/{importer-BWY2FFc5.js → importer-BfIsp1f9.js} +255 -77
- package/dist/chunks/importer-BfIsp1f9.js.map +1 -0
- package/dist/chunks/{importer-zGLo8Dg_.js → importer-C_TEA1zJ.js} +484 -157
- package/dist/chunks/importer-C_TEA1zJ.js.map +1 -0
- package/dist/chunks/{importer-DET1tg6s.js → importer-DCjOvO7U.js} +1687 -310
- package/dist/chunks/importer-DCjOvO7U.js.map +1 -0
- package/dist/chunks/{importer-vi3bSdtw.js → importer-DfVu6j59.js} +509 -76
- package/dist/chunks/importer-DfVu6j59.js.map +1 -0
- package/dist/chunks/{importer-DIrbbnAf.js → importer-Dku_VAIY.js} +585 -226
- package/dist/chunks/importer-Dku_VAIY.js.map +1 -0
- package/dist/chunks/json-elements-3tnyt5uZ.js +1225 -0
- package/dist/chunks/json-elements-3tnyt5uZ.js.map +1 -0
- package/dist/chunks/ontology-export-B9Tcf37D.js +357 -0
- package/dist/chunks/ontology-export-B9Tcf37D.js.map +1 -0
- package/dist/chunks/{records-dbRkxwaq.js → records-CQXT__mY.js} +298 -114
- package/dist/chunks/records-CQXT__mY.js.map +1 -0
- package/dist/chunks/{text-Dr0Ifpag.js → text-DmcUs5_Q.js} +29 -11
- package/dist/chunks/text-DmcUs5_Q.js.map +1 -0
- package/dist/chunks/{writer-C7flM-Ih.js → writer-DxjgHwSe.js} +19 -1
- package/dist/chunks/writer-DxjgHwSe.js.map +1 -0
- package/dist/csv.js +951 -261
- package/dist/csv.js.map +1 -1
- package/dist/cx.js +6 -3
- package/dist/cx2.js +2 -2
- package/dist/cys.js +6 -3
- package/dist/dot.js +1 -1
- package/dist/gexf.js +554 -122
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +342 -85
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +1105 -213
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +3 -2
- package/dist/graphml.js.map +1 -1
- package/dist/json.js +3 -2
- package/dist/neo4j.js +564 -157
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +6 -3
- package/dist/pajek.js +1 -1
- package/dist/src/children.d.ts +18 -6
- package/dist/src/children.d.ts.map +1 -1
- package/dist/src/children.js +6 -3
- package/dist/src/children.js.map +1 -1
- package/dist/src/common/attributes.d.ts +29 -11
- package/dist/src/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +25 -7
- package/dist/src/common/attributes.js.map +1 -1
- package/dist/src/common/cell-budget.d.ts +9 -2
- package/dist/src/common/cell-budget.d.ts.map +1 -1
- package/dist/src/common/cell-budget.js +11 -3
- package/dist/src/common/cell-budget.js.map +1 -1
- package/dist/src/common/choose.d.ts +19 -0
- package/dist/src/common/choose.d.ts.map +1 -0
- package/dist/src/common/choose.js +72 -0
- package/dist/src/common/choose.js.map +1 -0
- package/dist/src/common/codes.d.ts +456 -66
- package/dist/src/common/codes.d.ts.map +1 -1
- package/dist/src/common/codes.js +458 -66
- package/dist/src/common/codes.js.map +1 -1
- package/dist/src/common/compare.d.ts +81 -0
- package/dist/src/common/compare.d.ts.map +1 -0
- package/dist/src/common/compare.js +394 -0
- package/dist/src/common/compare.js.map +1 -0
- package/dist/src/common/cx-export.d.ts +123 -0
- package/dist/src/common/cx-export.d.ts.map +1 -0
- package/dist/src/common/cx-export.js +334 -0
- package/dist/src/common/cx-export.js.map +1 -0
- package/dist/src/common/declared-types.d.ts +22 -2
- package/dist/src/common/declared-types.d.ts.map +1 -1
- package/dist/src/common/declared-types.js +19 -1
- package/dist/src/common/declared-types.js.map +1 -1
- package/dist/src/common/direction.d.ts +74 -50
- package/dist/src/common/direction.d.ts.map +1 -1
- package/dist/src/common/direction.js +47 -27
- package/dist/src/common/direction.js.map +1 -1
- package/dist/src/common/escape.d.ts +21 -9
- package/dist/src/common/escape.d.ts.map +1 -1
- package/dist/src/common/escape.js +33 -17
- package/dist/src/common/escape.js.map +1 -1
- package/dist/src/common/export.d.ts +144 -32
- package/dist/src/common/export.d.ts.map +1 -1
- package/dist/src/common/export.js +216 -42
- package/dist/src/common/export.js.map +1 -1
- package/dist/src/common/format.d.ts +23 -1
- package/dist/src/common/format.d.ts.map +1 -1
- package/dist/src/common/format.js +26 -1
- package/dist/src/common/format.js.map +1 -1
- package/dist/src/common/ids.d.ts +9 -2
- package/dist/src/common/ids.d.ts.map +1 -1
- package/dist/src/common/ids.js +6 -2
- package/dist/src/common/ids.js.map +1 -1
- package/dist/src/common/input.d.ts +122 -28
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +890 -124
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/json-elements.d.ts +144 -20
- package/dist/src/common/json-elements.d.ts.map +1 -1
- package/dist/src/common/json-elements.js +868 -188
- package/dist/src/common/json-elements.js.map +1 -1
- package/dist/src/common/line-format.d.ts +90 -0
- package/dist/src/common/line-format.d.ts.map +1 -0
- package/dist/src/common/line-format.js +185 -0
- package/dist/src/common/line-format.js.map +1 -0
- package/dist/src/common/lists.d.ts +6 -1
- package/dist/src/common/lists.d.ts.map +1 -1
- package/dist/src/common/lists.js +2 -0
- package/dist/src/common/lists.js.map +1 -1
- package/dist/src/common/ontology-export.d.ts +167 -0
- package/dist/src/common/ontology-export.d.ts.map +1 -0
- package/dist/src/common/ontology-export.js +359 -0
- package/dist/src/common/ontology-export.js.map +1 -0
- package/dist/src/common/ontology.d.ts +25 -3
- package/dist/src/common/ontology.d.ts.map +1 -1
- package/dist/src/common/ontology.js +30 -9
- package/dist/src/common/ontology.js.map +1 -1
- package/dist/src/common/options.d.ts +62 -12
- package/dist/src/common/options.d.ts.map +1 -1
- package/dist/src/common/options.js +89 -11
- package/dist/src/common/options.js.map +1 -1
- package/dist/src/common/plural.d.ts +31 -0
- package/dist/src/common/plural.d.ts.map +1 -0
- package/dist/src/common/plural.js +46 -0
- package/dist/src/common/plural.js.map +1 -0
- package/dist/src/common/report.d.ts +72 -14
- package/dist/src/common/report.d.ts.map +1 -1
- package/dist/src/common/report.js +146 -22
- package/dist/src/common/report.js.map +1 -1
- package/dist/src/common/temporal.d.ts +22 -5
- package/dist/src/common/temporal.d.ts.map +1 -1
- package/dist/src/common/temporal.js +13 -2
- package/dist/src/common/temporal.js.map +1 -1
- package/dist/src/common/text.d.ts +39 -23
- package/dist/src/common/text.d.ts.map +1 -1
- package/dist/src/common/text.js +51 -21
- package/dist/src/common/text.js.map +1 -1
- package/dist/src/common/weights.d.ts +37 -4
- package/dist/src/common/weights.d.ts.map +1 -1
- package/dist/src/common/weights.js +61 -2
- package/dist/src/common/weights.js.map +1 -1
- package/dist/src/common/writer.d.ts +19 -3
- package/dist/src/common/writer.d.ts.map +1 -1
- package/dist/src/common/writer.js +30 -2
- package/dist/src/common/writer.js.map +1 -1
- package/dist/src/common/xml.d.ts +125 -9
- package/dist/src/common/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +400 -83
- package/dist/src/common/xml.js.map +1 -1
- package/dist/src/common/zip.d.ts +44 -4
- package/dist/src/common/zip.d.ts.map +1 -1
- package/dist/src/common/zip.js +134 -5
- package/dist/src/common/zip.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts +80 -24
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +145 -32
- package/dist/src/formats/csv/exporter.js.map +1 -1
- package/dist/src/formats/csv/header.d.ts +38 -9
- package/dist/src/formats/csv/header.d.ts.map +1 -1
- package/dist/src/formats/csv/header.js +46 -10
- package/dist/src/formats/csv/header.js.map +1 -1
- package/dist/src/formats/csv/importer.d.ts +187 -37
- package/dist/src/formats/csv/importer.d.ts.map +1 -1
- package/dist/src/formats/csv/importer.js +835 -204
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/index.d.ts +100 -19
- package/dist/src/formats/csv/index.d.ts.map +1 -1
- package/dist/src/formats/csv/index.js +94 -21
- package/dist/src/formats/csv/index.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts +95 -10
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +333 -122
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/csv/values.d.ts +1 -0
- package/dist/src/formats/csv/values.d.ts.map +1 -1
- package/dist/src/formats/csv/values.js +1 -0
- package/dist/src/formats/csv/values.js.map +1 -1
- package/dist/src/formats/cx/exporter.d.ts +102 -0
- package/dist/src/formats/cx/exporter.d.ts.map +1 -0
- package/dist/src/formats/cx/exporter.js +1217 -0
- package/dist/src/formats/cx/exporter.js.map +1 -0
- package/dist/src/formats/cx/importer.d.ts +79 -32
- package/dist/src/formats/cx/importer.d.ts.map +1 -1
- package/dist/src/formats/cx/importer.js +680 -285
- package/dist/src/formats/cx/importer.js.map +1 -1
- package/dist/src/formats/cx/index.d.ts +4 -3
- package/dist/src/formats/cx/index.d.ts.map +1 -1
- package/dist/src/formats/cx/index.js +4 -3
- package/dist/src/formats/cx/index.js.map +1 -1
- package/dist/src/formats/cx2/exporter.d.ts +29 -11
- package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
- package/dist/src/formats/cx2/exporter.js +39 -268
- package/dist/src/formats/cx2/exporter.js.map +1 -1
- package/dist/src/formats/cx2/importer.d.ts +78 -28
- package/dist/src/formats/cx2/importer.d.ts.map +1 -1
- package/dist/src/formats/cx2/importer.js +226 -79
- package/dist/src/formats/cx2/importer.js.map +1 -1
- package/dist/src/formats/cx2/index.d.ts +2 -1
- package/dist/src/formats/cx2/index.d.ts.map +1 -1
- package/dist/src/formats/cx2/index.js +2 -1
- package/dist/src/formats/cx2/index.js.map +1 -1
- package/dist/src/formats/cys/constants.d.ts +66 -17
- package/dist/src/formats/cys/constants.d.ts.map +1 -1
- package/dist/src/formats/cys/constants.js +59 -18
- package/dist/src/formats/cys/constants.js.map +1 -1
- package/dist/src/formats/cys/exporter.d.ts +118 -0
- package/dist/src/formats/cys/exporter.d.ts.map +1 -0
- package/dist/src/formats/cys/exporter.js +1072 -0
- package/dist/src/formats/cys/exporter.js.map +1 -0
- package/dist/src/formats/cys/importer.d.ts +22 -8
- package/dist/src/formats/cys/importer.d.ts.map +1 -1
- package/dist/src/formats/cys/importer.js +510 -149
- package/dist/src/formats/cys/importer.js.map +1 -1
- package/dist/src/formats/cys/index.d.ts +4 -2
- package/dist/src/formats/cys/index.d.ts.map +1 -1
- package/dist/src/formats/cys/index.js +4 -2
- package/dist/src/formats/cys/index.js.map +1 -1
- package/dist/src/formats/cys/session.d.ts +46 -7
- package/dist/src/formats/cys/session.d.ts.map +1 -1
- package/dist/src/formats/cys/session.js +98 -24
- package/dist/src/formats/cys/session.js.map +1 -1
- package/dist/src/formats/cys/tables.d.ts +9 -2
- package/dist/src/formats/cys/tables.d.ts.map +1 -1
- package/dist/src/formats/cys/tables.js +29 -7
- package/dist/src/formats/cys/tables.js.map +1 -1
- package/dist/src/formats/dot/exporter.d.ts +42 -14
- package/dist/src/formats/dot/exporter.d.ts.map +1 -1
- package/dist/src/formats/dot/exporter.js +56 -29
- package/dist/src/formats/dot/exporter.js.map +1 -1
- package/dist/src/formats/dot/importer.d.ts +91 -29
- package/dist/src/formats/dot/importer.d.ts.map +1 -1
- package/dist/src/formats/dot/importer.js +190 -39
- package/dist/src/formats/dot/importer.js.map +1 -1
- package/dist/src/formats/dot/index.d.ts +2 -1
- package/dist/src/formats/dot/index.d.ts.map +1 -1
- package/dist/src/formats/dot/index.js +2 -1
- package/dist/src/formats/dot/index.js.map +1 -1
- package/dist/src/formats/dot/names.d.ts +44 -11
- package/dist/src/formats/dot/names.d.ts.map +1 -1
- package/dist/src/formats/dot/names.js +44 -11
- package/dist/src/formats/dot/names.js.map +1 -1
- package/dist/src/formats/dot/tokenizer.d.ts +9 -2
- package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
- package/dist/src/formats/dot/tokenizer.js +20 -5
- package/dist/src/formats/dot/tokenizer.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts +46 -15
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +45 -15
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts +87 -23
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +471 -90
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gexf/index.d.ts +84 -21
- package/dist/src/formats/gexf/index.d.ts.map +1 -1
- package/dist/src/formats/gexf/index.js +80 -23
- package/dist/src/formats/gexf/index.js.map +1 -1
- package/dist/src/formats/gexf/schema.d.ts +67 -15
- package/dist/src/formats/gexf/schema.d.ts.map +1 -1
- package/dist/src/formats/gexf/schema.js +59 -13
- package/dist/src/formats/gexf/schema.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts +69 -21
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +74 -29
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts +116 -25
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +290 -61
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/index.d.ts +87 -24
- package/dist/src/formats/gml/index.d.ts.map +1 -1
- package/dist/src/formats/gml/index.js +79 -26
- package/dist/src/formats/gml/index.js.map +1 -1
- package/dist/src/formats/gml/syntax.d.ts +67 -18
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +108 -26
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/constants.d.ts +174 -44
- package/dist/src/formats/graphml/constants.d.ts.map +1 -1
- package/dist/src/formats/graphml/constants.js +165 -44
- package/dist/src/formats/graphml/constants.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts +17 -9
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +33 -12
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts +19 -7
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +466 -39
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/index.d.ts +2 -1
- package/dist/src/formats/graphml/index.d.ts.map +1 -1
- package/dist/src/formats/graphml/index.js +2 -1
- package/dist/src/formats/graphml/index.js.map +1 -1
- package/dist/src/formats/graphml/tree.d.ts +6 -2
- package/dist/src/formats/graphml/tree.d.ts.map +1 -1
- package/dist/src/formats/graphml/tree.js +6 -2
- package/dist/src/formats/graphml/tree.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts +8 -3
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +36 -15
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/dialect.d.ts +88 -24
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +109 -19
- package/dist/src/formats/json/dialect.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts +134 -22
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +135 -54
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts +173 -52
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +899 -220
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/index.d.ts +3 -2
- package/dist/src/formats/json/index.d.ts.map +1 -1
- package/dist/src/formats/json/index.js +3 -2
- package/dist/src/formats/json/index.js.map +1 -1
- package/dist/src/formats/json/obographs-export.d.ts +68 -0
- package/dist/src/formats/json/obographs-export.d.ts.map +1 -0
- package/dist/src/formats/json/obographs-export.js +941 -0
- package/dist/src/formats/json/obographs-export.js.map +1 -0
- package/dist/src/formats/json/obographs.d.ts +26 -0
- package/dist/src/formats/json/obographs.d.ts.map +1 -1
- package/dist/src/formats/json/obographs.js +297 -80
- package/dist/src/formats/json/obographs.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts +109 -21
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +238 -85
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/neo4j/header.d.ts +17 -4
- package/dist/src/formats/neo4j/header.d.ts.map +1 -1
- package/dist/src/formats/neo4j/header.js +36 -4
- package/dist/src/formats/neo4j/header.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts +107 -27
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +415 -63
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/neo4j/index.d.ts +73 -23
- package/dist/src/formats/neo4j/index.d.ts.map +1 -1
- package/dist/src/formats/neo4j/index.js +65 -25
- package/dist/src/formats/neo4j/index.js.map +1 -1
- package/dist/src/formats/obo/exporter.d.ts +150 -0
- package/dist/src/formats/obo/exporter.d.ts.map +1 -0
- package/dist/src/formats/obo/exporter.js +1736 -0
- package/dist/src/formats/obo/exporter.js.map +1 -0
- package/dist/src/formats/obo/importer.d.ts +95 -19
- package/dist/src/formats/obo/importer.d.ts.map +1 -1
- package/dist/src/formats/obo/importer.js +475 -104
- package/dist/src/formats/obo/importer.js.map +1 -1
- package/dist/src/formats/obo/index.d.ts +6 -3
- package/dist/src/formats/obo/index.d.ts.map +1 -1
- package/dist/src/formats/obo/index.js +6 -3
- package/dist/src/formats/obo/index.js.map +1 -1
- package/dist/src/formats/obo/syntax.d.ts +108 -4
- package/dist/src/formats/obo/syntax.d.ts.map +1 -1
- package/dist/src/formats/obo/syntax.js +211 -34
- package/dist/src/formats/obo/syntax.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts +54 -16
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +66 -34
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts +84 -22
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +303 -83
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/index.d.ts +2 -1
- package/dist/src/formats/pajek/index.d.ts.map +1 -1
- package/dist/src/formats/pajek/index.js +2 -1
- package/dist/src/formats/pajek/index.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts +77 -14
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +183 -51
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/columns.d.ts +1 -0
- package/dist/src/formats/xgmml/columns.d.ts.map +1 -1
- package/dist/src/formats/xgmml/columns.js +4 -0
- package/dist/src/formats/xgmml/columns.js.map +1 -1
- package/dist/src/formats/xgmml/constants.d.ts +186 -56
- package/dist/src/formats/xgmml/constants.d.ts.map +1 -1
- package/dist/src/formats/xgmml/constants.js +181 -57
- package/dist/src/formats/xgmml/constants.js.map +1 -1
- package/dist/src/formats/xgmml/document.d.ts +65 -11
- package/dist/src/formats/xgmml/document.d.ts.map +1 -1
- package/dist/src/formats/xgmml/document.js +157 -28
- package/dist/src/formats/xgmml/document.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +61 -3
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +129 -26
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/exporter.d.ts +13 -6
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
- package/dist/src/formats/xgmml/exporter.js +20 -21
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/importer.d.ts +33 -12
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
- package/dist/src/formats/xgmml/importer.js +35 -72
- package/dist/src/formats/xgmml/importer.js.map +1 -1
- package/dist/src/formats/xgmml/index.d.ts +1 -0
- package/dist/src/formats/xgmml/index.d.ts.map +1 -1
- package/dist/src/formats/xgmml/index.js +1 -0
- package/dist/src/formats/xgmml/index.js.map +1 -1
- package/dist/src/formats/xgmml/values.d.ts +8 -1
- package/dist/src/formats/xgmml/values.d.ts.map +1 -1
- package/dist/src/formats/xgmml/values.js +7 -2
- package/dist/src/formats/xgmml/values.js.map +1 -1
- package/dist/src/index.d.ts +23 -18
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +28 -16
- package/dist/src/index.js.map +1 -1
- package/dist/src/registry.d.ts +478 -92
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +807 -88
- package/dist/src/registry.js.map +1 -1
- package/dist/src/sniff.d.ts +61 -19
- package/dist/src/sniff.d.ts.map +1 -1
- package/dist/src/sniff.js +121 -29
- package/dist/src/sniff.js.map +1 -1
- package/dist/src/types.d.ts +362 -112
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +28 -7
- package/dist/src/types.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/docs/samples/README.md +25 -0
- package/docs/samples/got-edges.csv +353 -0
- package/docs/samples/got-network.graphml +1384 -0
- package/docs/samples/got-nodes.csv +108 -0
- package/docs/samples/got.cx +930 -0
- package/docs/samples/got.cx2 +465 -0
- package/docs/samples/got.gexf +471 -0
- package/docs/samples/got.gml +2650 -0
- package/docs/samples/got.json +2546 -0
- package/docs/samples/got.net +461 -0
- package/docs/samples/movies-nodes.csv +9 -0
- package/docs/samples/movies-rels.csv +8 -0
- package/docs/samples/networks.cys +0 -0
- package/docs/samples/proteins.xgmml +9 -0
- package/docs/samples/teams.gv +24 -0
- package/docs/samples/vehicles.obo +25 -0
- package/package.json +9 -4
- package/src/children.ts +18 -6
- package/src/common/attributes.ts +29 -11
- package/src/common/cell-budget.ts +11 -3
- package/src/common/choose.ts +89 -0
- package/src/common/codes.ts +490 -66
- package/src/common/compare.ts +472 -0
- package/src/common/cx-export.ts +432 -0
- package/src/common/declared-types.ts +38 -4
- package/src/common/direction.ts +78 -51
- package/src/common/escape.ts +33 -17
- package/src/common/export.ts +284 -47
- package/src/common/format.ts +30 -1
- package/src/common/ids.ts +14 -3
- package/src/common/input.ts +1057 -134
- package/src/common/json-elements.ts +1034 -198
- package/src/common/line-format.ts +249 -0
- package/src/common/lists.ts +6 -1
- package/src/common/ontology-export.ts +461 -0
- package/src/common/ontology.ts +31 -9
- package/src/common/options.ts +110 -15
- package/src/common/plural.ts +49 -0
- package/src/common/report.ts +168 -25
- package/src/common/temporal.ts +22 -5
- package/src/common/text.ts +62 -23
- package/src/common/weights.ts +74 -4
- package/src/common/writer.ts +37 -3
- package/src/common/xml.ts +477 -88
- package/src/common/zip.ts +169 -7
- package/src/formats/csv/exporter.ts +201 -46
- package/src/formats/csv/header.ts +50 -11
- package/src/formats/csv/importer.ts +1133 -245
- package/src/formats/csv/index.ts +106 -19
- package/src/formats/csv/records.ts +396 -122
- package/src/formats/csv/values.ts +1 -0
- package/src/formats/cx/exporter.ts +1483 -0
- package/src/formats/cx/importer.ts +888 -300
- package/src/formats/cx/index.ts +4 -3
- package/src/formats/cx2/exporter.ts +61 -338
- package/src/formats/cx2/importer.ts +317 -86
- package/src/formats/cx2/index.ts +2 -1
- package/src/formats/cys/constants.ts +62 -17
- package/src/formats/cys/exporter.ts +1368 -0
- package/src/formats/cys/importer.ts +659 -157
- package/src/formats/cys/index.ts +4 -2
- package/src/formats/cys/session.ts +139 -31
- package/src/formats/cys/tables.ts +47 -9
- package/src/formats/dot/exporter.ts +83 -37
- package/src/formats/dot/importer.ts +248 -54
- package/src/formats/dot/index.ts +2 -1
- package/src/formats/dot/names.ts +44 -11
- package/src/formats/dot/tokenizer.ts +25 -6
- package/src/formats/gexf/exporter.ts +57 -18
- package/src/formats/gexf/importer.ts +636 -105
- package/src/formats/gexf/index.ts +90 -21
- package/src/formats/gexf/schema.ts +67 -15
- package/src/formats/gml/exporter.ts +90 -36
- package/src/formats/gml/importer.ts +388 -67
- package/src/formats/gml/index.ts +85 -24
- package/src/formats/gml/syntax.ts +111 -26
- package/src/formats/graphml/constants.ts +178 -44
- package/src/formats/graphml/exporter.ts +52 -18
- package/src/formats/graphml/importer.ts +640 -60
- package/src/formats/graphml/index.ts +2 -1
- package/src/formats/graphml/tree.ts +6 -2
- package/src/formats/graphml/yfiles.ts +37 -15
- package/src/formats/json/dialect.ts +128 -29
- package/src/formats/json/exporter.ts +205 -68
- package/src/formats/json/importer.ts +1163 -253
- package/src/formats/json/index.ts +3 -1
- package/src/formats/json/obographs-export.ts +1197 -0
- package/src/formats/json/obographs.ts +353 -92
- package/src/formats/neo4j/exporter.ts +320 -101
- package/src/formats/neo4j/header.ts +44 -6
- package/src/formats/neo4j/importer.ts +512 -73
- package/src/formats/neo4j/index.ts +75 -23
- package/src/formats/obo/exporter.ts +2210 -0
- package/src/formats/obo/importer.ts +622 -109
- package/src/formats/obo/index.ts +6 -3
- package/src/formats/obo/syntax.ts +232 -37
- package/src/formats/pajek/exporter.ts +89 -41
- package/src/formats/pajek/importer.ts +407 -93
- package/src/formats/pajek/index.ts +2 -1
- package/src/formats/pajek/syntax.ts +213 -52
- package/src/formats/xgmml/columns.ts +10 -0
- package/src/formats/xgmml/constants.ts +183 -56
- package/src/formats/xgmml/document.ts +212 -34
- package/src/formats/xgmml/emit.ts +175 -34
- package/src/formats/xgmml/exporter.ts +35 -27
- package/src/formats/xgmml/importer.ts +64 -86
- package/src/formats/xgmml/index.ts +1 -0
- package/src/formats/xgmml/values.ts +11 -3
- package/src/index.ts +81 -11
- package/src/registry.ts +1121 -135
- package/src/sniff.ts +163 -32
- package/src/types.ts +385 -114
- package/dist/chunks/escape-B-tkk_Cl.js.map +0 -1
- package/dist/chunks/export-Bh60Dv-n.js +0 -582
- package/dist/chunks/export-Bh60Dv-n.js.map +0 -1
- package/dist/chunks/exporter-BGrsWamJ.js.map +0 -1
- package/dist/chunks/exporter-DIeGJXAZ.js.map +0 -1
- package/dist/chunks/importer-BWY2FFc5.js.map +0 -1
- package/dist/chunks/importer-DD-xv9_Y.js +0 -1471
- package/dist/chunks/importer-DD-xv9_Y.js.map +0 -1
- package/dist/chunks/importer-DET1tg6s.js.map +0 -1
- package/dist/chunks/importer-DEcKhsmQ.js +0 -1614
- package/dist/chunks/importer-DEcKhsmQ.js.map +0 -1
- package/dist/chunks/importer-DIrbbnAf.js.map +0 -1
- package/dist/chunks/importer-D_e7e7LX.js.map +0 -1
- package/dist/chunks/importer-eUREhBGj.js.map +0 -1
- package/dist/chunks/importer-vi3bSdtw.js.map +0 -1
- package/dist/chunks/importer-zGLo8Dg_.js.map +0 -1
- package/dist/chunks/json-elements-DDS8N2Dd.js +0 -779
- package/dist/chunks/json-elements-DDS8N2Dd.js.map +0 -1
- package/dist/chunks/ontology-BnrJ4I98.js +0 -113
- package/dist/chunks/ontology-BnrJ4I98.js.map +0 -1
- package/dist/chunks/records-dbRkxwaq.js.map +0 -1
- package/dist/chunks/report-B1z4WT9e.js +0 -2372
- package/dist/chunks/report-B1z4WT9e.js.map +0 -1
- package/dist/chunks/text-Dr0Ifpag.js.map +0 -1
- package/dist/chunks/weights-Dzba96G3.js +0 -176
- package/dist/chunks/weights-Dzba96G3.js.map +0 -1
- package/dist/chunks/writer-C7flM-Ih.js.map +0 -1
package/dist/src/registry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The importer / exporter registry
|
|
2
|
+
* The importer / exporter registry: the built-in formats
|
|
3
3
|
* registered by name, `sniff()` over them, and the two conveniences for callers who do not own a
|
|
4
4
|
* sink: `importGraph()` sniffs the format, creates a builder seeded from the common options
|
|
5
5
|
* (`directed: true` as a placeholder; the importer sets the real value), imports and freezes;
|
|
@@ -8,24 +8,71 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { GraphFormatError, } from "@graphty/graph-format";
|
|
10
10
|
import { CellBudgetBuilder, maxEmptyCellsOption } from "./common/cell-budget.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { EDGES_MERGED_CODE, EMPTY_INPUT_CODE, FETCH_CODE, OPTION_IGNORED_CODE, SELF_LOOPS_DROPPED_CODE, UNKNOWN_OPTION_CODE, WEIGHT_NOT_FOUND_CODE, } from "./common/codes.js";
|
|
12
|
+
import { abortable, foreignKind, lockReader, normalizeInput, throwIfAborted } from "./common/input.js";
|
|
13
|
+
import { chooseGraph, resolveImportOptions } from "./common/options.js";
|
|
14
|
+
import { agree, plural } from "./common/plural.js";
|
|
15
|
+
import { ImportReportBuilder, isAbortError, messageOf } from "./common/report.js";
|
|
16
|
+
import { collectBytes } from "./common/writer.js";
|
|
13
17
|
import { csvExporter, csvImporter } from "./formats/csv/index.js";
|
|
14
|
-
import { cxImporter } from "./formats/cx/index.js";
|
|
18
|
+
import { cxExporter, cxImporter } from "./formats/cx/index.js";
|
|
15
19
|
import { cx2Exporter, cx2Importer } from "./formats/cx2/index.js";
|
|
16
|
-
import { cysImporter } from "./formats/cys/index.js";
|
|
20
|
+
import { cysExporter, cysImporter } from "./formats/cys/index.js";
|
|
17
21
|
import { dotExporter, dotImporter } from "./formats/dot/index.js";
|
|
18
22
|
import { gexfExporter, gexfImporter } from "./formats/gexf/index.js";
|
|
19
23
|
import { gmlExporter, gmlImporter } from "./formats/gml/index.js";
|
|
20
24
|
import { graphmlExporter, graphmlImporter } from "./formats/graphml/index.js";
|
|
21
25
|
import { jsonExporter, jsonImporter } from "./formats/json/index.js";
|
|
22
26
|
import { neo4jExporter, neo4jImporter } from "./formats/neo4j/index.js";
|
|
23
|
-
import { oboImporter } from "./formats/obo/index.js";
|
|
27
|
+
import { oboExporter, oboImporter } from "./formats/obo/index.js";
|
|
24
28
|
import { pajekExporter, pajekImporter } from "./formats/pajek/index.js";
|
|
25
29
|
import { xgmmlExporter, xgmmlImporter } from "./formats/xgmml/index.js";
|
|
26
30
|
import { rankFormats, SNIFF_HEAD_BYTES } from "./sniff.js";
|
|
27
|
-
|
|
31
|
+
import { ImportError, } from "./types.js";
|
|
32
|
+
/**
|
|
33
|
+
* The input is in no format graph-io can read (or none of the formats you registered, for your own FormatRegistry).
|
|
34
|
+
* Pass `format` when you know what the file is.
|
|
35
|
+
* @category Issue and loss codes
|
|
36
|
+
*/
|
|
28
37
|
export const UNKNOWN_FORMAT_CODE = "E_UNKNOWN_FORMAT";
|
|
38
|
+
/**
|
|
39
|
+
* Registered importer whose sniff() threw while the format was being chosen; that importer was treated as not
|
|
40
|
+
* recognizing the input (a defect in that importer).
|
|
41
|
+
* @category Issue and loss codes
|
|
42
|
+
*/
|
|
43
|
+
export const SNIFF_FAILED_CODE = "W_SNIFF_FAILED";
|
|
44
|
+
/**
|
|
45
|
+
* The options the load and save functions take themselves: the common import and export options and the functions'
|
|
46
|
+
* own. With the formats' own `options` lists, these are the names a load or a save knows.
|
|
47
|
+
*/
|
|
48
|
+
const FUNCTION_OPTIONS = new Set([
|
|
49
|
+
"addMissingNodes",
|
|
50
|
+
"builder",
|
|
51
|
+
"defaultDirected",
|
|
52
|
+
"duplicateEdges",
|
|
53
|
+
"encoding",
|
|
54
|
+
"errorLimit",
|
|
55
|
+
"filename",
|
|
56
|
+
"format",
|
|
57
|
+
"freeze",
|
|
58
|
+
"graphIndex",
|
|
59
|
+
"graphName",
|
|
60
|
+
"hyperedges",
|
|
61
|
+
"ids",
|
|
62
|
+
"long",
|
|
63
|
+
"maxEmptyCells",
|
|
64
|
+
"mimeType",
|
|
65
|
+
"nodeIdFrom",
|
|
66
|
+
"onMixedDirection",
|
|
67
|
+
"onProgress",
|
|
68
|
+
"request",
|
|
69
|
+
"restoreMangledIds",
|
|
70
|
+
"sanitizeIds",
|
|
71
|
+
"selfLoops",
|
|
72
|
+
"signal",
|
|
73
|
+
"weightDtype",
|
|
74
|
+
"weightFrom",
|
|
75
|
+
]);
|
|
29
76
|
/** The keys of ImportGraphOptions that belong to the registry, never to an importer. */
|
|
30
77
|
const REGISTRY_KEYS = new Set([
|
|
31
78
|
"format",
|
|
@@ -37,8 +84,9 @@ const REGISTRY_KEYS = new Set([
|
|
|
37
84
|
]);
|
|
38
85
|
/**
|
|
39
86
|
* A registry of importers and exporters by format name. Registration order is the tie-break
|
|
40
|
-
* order of sniffing
|
|
87
|
+
* order of sniffing; the default registry lists the built-in formats in the
|
|
41
88
|
* order of GRAPH_FORMATS.
|
|
89
|
+
* @category Formats and detection
|
|
42
90
|
*/
|
|
43
91
|
export class FormatRegistry {
|
|
44
92
|
constructor() {
|
|
@@ -126,10 +174,10 @@ export class FormatRegistry {
|
|
|
126
174
|
return [...new Set([...this.importerMap.keys(), ...this.exporterMap.keys()])];
|
|
127
175
|
}
|
|
128
176
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* @param hints -
|
|
132
|
-
* @returns the candidates, best first; empty when
|
|
177
|
+
* Every format that could be the input, best first, from the same hints as sniff(): use it to
|
|
178
|
+
* offer the user a choice when the best guess is not certain.
|
|
179
|
+
* @param hints - `{ filename, mimeType, head }`, any of them
|
|
180
|
+
* @returns the candidates, best first; empty when no format claims the input
|
|
133
181
|
*/
|
|
134
182
|
sniffAll(hints) {
|
|
135
183
|
return rankFormats(hints, this.importerMap.values());
|
|
@@ -144,40 +192,116 @@ export class FormatRegistry {
|
|
|
144
192
|
return ranked.length > 0 ? ranked[0] : null;
|
|
145
193
|
}
|
|
146
194
|
/**
|
|
147
|
-
* Read
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* An input holding several graphs yields the first, with a warning naming how many were
|
|
152
|
-
* skipped; importAllGraphs() returns every one.
|
|
195
|
+
* Read a graph from a string, bytes or a stream. The format is the one you name in `format`,
|
|
196
|
+
* else it is detected from `filename`, `mimeType` and the first bytes of the content. From an
|
|
197
|
+
* input that holds several graphs, it reads the one `graphIndex` or `graphName` chooses, else
|
|
198
|
+
* the first, with a W_MULTIPLE_GRAPHS warning; importAllGraphs() returns every one.
|
|
153
199
|
* @param input - the text, bytes, stream or chunks to read
|
|
154
200
|
* @param options - the format, hints, common and format-specific import options
|
|
155
|
-
* @returns the snapshot, the
|
|
201
|
+
* @returns the graph (`snapshot`), the format it was read as, and the import report
|
|
156
202
|
*/
|
|
157
203
|
async importGraph(input, options = {}) {
|
|
158
|
-
const
|
|
159
|
-
const
|
|
204
|
+
const opts = options;
|
|
205
|
+
const picked = await this.choose(input, opts);
|
|
206
|
+
const unknown = this.unknownOptions(opts).map((name) => Object.freeze({
|
|
207
|
+
category: "unsupported",
|
|
208
|
+
severity: "warning",
|
|
209
|
+
code: UNKNOWN_OPTION_CODE,
|
|
210
|
+
message: unknownOptionText(name),
|
|
211
|
+
line: null,
|
|
212
|
+
element: name,
|
|
213
|
+
}));
|
|
214
|
+
const chosen = unknown.length === 0 ? picked : { ...picked, warnings: [...picked.warnings, ...unknown] };
|
|
215
|
+
const { importer } = chosen;
|
|
216
|
+
if ((opts.graphIndex !== undefined || opts.graphName !== undefined) &&
|
|
217
|
+
importer.importAll !== undefined &&
|
|
218
|
+
importer.listGraphs === undefined) {
|
|
219
|
+
// an importer that reads several graphs but does not choose among them itself (DOT, GML,
|
|
220
|
+
// Pajek): read them all and return the chosen one, so the choice is never ignored
|
|
221
|
+
const all = await this.readAll(chosen, opts);
|
|
222
|
+
const names = all.map((r) => r.snapshot.meta.name);
|
|
223
|
+
return all[chooseGraph(names, opts, new ImportReportBuilder(importer.format, 0))];
|
|
224
|
+
}
|
|
225
|
+
let builder;
|
|
160
226
|
let report;
|
|
161
227
|
try {
|
|
162
|
-
|
|
228
|
+
builder = seededBuilder(opts, chosen.importer.format);
|
|
229
|
+
report = await chosen.importer.import(chosen.source, builder, importerOptions(opts));
|
|
163
230
|
}
|
|
164
231
|
catch (err) {
|
|
165
232
|
await chosen.peeked?.close();
|
|
166
|
-
throw err;
|
|
233
|
+
throw chosen.sniff === null ? notReadableAs(err, importer.format, opts.filename) : err;
|
|
234
|
+
}
|
|
235
|
+
return result(chosen, builder, report, opts);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Fetch a graph file from a URL and load it; see the top-level loadFromUrl() for the full
|
|
239
|
+
* description.
|
|
240
|
+
* @param url - an absolute URL, or in a browser one relative to the page
|
|
241
|
+
* @param options - import options, plus `request` for fetch
|
|
242
|
+
* @returns the graph, the format it was read as, and the import report
|
|
243
|
+
*/
|
|
244
|
+
async loadFromUrl(url, options = {}) {
|
|
245
|
+
const opts = options;
|
|
246
|
+
const { request, ...rest } = opts;
|
|
247
|
+
const signal = request?.signal ?? rest.signal ?? null;
|
|
248
|
+
const method = request?.method ?? "GET";
|
|
249
|
+
const where = `${method} ${String(url)}`;
|
|
250
|
+
let response;
|
|
251
|
+
try {
|
|
252
|
+
response = await fetch(url, { signal: rest.signal, ...request });
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
if (signal?.aborted === true || isAbortError(err)) {
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
258
|
+
return fetchFailed(rest, url, `${where} failed: ${fetchErrorText(url, err)}`, null, err);
|
|
259
|
+
}
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
await response.body?.cancel().catch(() => undefined);
|
|
262
|
+
const status = `${String(response.status)} ${response.statusText}`.trim();
|
|
263
|
+
return fetchFailed(rest, url, `${where} failed: ${status}`, response.status, null);
|
|
167
264
|
}
|
|
168
|
-
return
|
|
265
|
+
return this.importGraph(response.body ?? new Uint8Array(0), {
|
|
266
|
+
...rest,
|
|
267
|
+
filename: rest.filename ?? urlFilename(url),
|
|
268
|
+
mimeType: rest.mimeType ?? response.headers.get("content-type"),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Load a graph from a File or Blob; see the top-level loadFromFile() for the full description.
|
|
273
|
+
* @param file - the File or Blob to read
|
|
274
|
+
* @param options - the same options as importGraph()
|
|
275
|
+
* @returns the graph, the format it was read as, and the import report
|
|
276
|
+
*/
|
|
277
|
+
loadFromFile(file, options = {}) {
|
|
278
|
+
const opts = options;
|
|
279
|
+
const { name } = file;
|
|
280
|
+
return this.importGraph(file.stream(), {
|
|
281
|
+
...opts,
|
|
282
|
+
filename: opts.filename ?? (typeof name === "string" ? name : null),
|
|
283
|
+
mimeType: opts.mimeType ?? (file.type === "" ? null : file.type),
|
|
284
|
+
});
|
|
169
285
|
}
|
|
170
286
|
/**
|
|
171
287
|
* Read every graph of an input (a DOT file with several graphs, a Pajek project with several
|
|
172
|
-
* networks, a JGF document with a `graphs` array), each
|
|
173
|
-
*
|
|
174
|
-
* entry. Options, sniffing and the builder seed are those of importGraph().
|
|
288
|
+
* networks, a JGF document with a `graphs` array), each as its own snapshot. A format that holds
|
|
289
|
+
* one graph per file gives one result. Options and detection are those of importGraph().
|
|
175
290
|
* @param input - the text, bytes, stream or chunks to read
|
|
176
291
|
* @param options - the format, hints, common and format-specific import options
|
|
177
292
|
* @returns one result per graph, in document order
|
|
178
293
|
*/
|
|
179
294
|
async importAllGraphs(input, options = {}) {
|
|
180
|
-
const
|
|
295
|
+
const opts = options;
|
|
296
|
+
return this.readAll(await this.choose(input, opts), opts);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Read every graph of an input with the chosen importer.
|
|
300
|
+
* @param chosen - the importer and the input to hand it
|
|
301
|
+
* @param options - the importGraph options
|
|
302
|
+
* @returns one result per graph, in document order
|
|
303
|
+
*/
|
|
304
|
+
async readAll(chosen, options) {
|
|
181
305
|
const { importer } = chosen;
|
|
182
306
|
const builders = [];
|
|
183
307
|
let reports;
|
|
@@ -196,9 +320,16 @@ export class FormatRegistry {
|
|
|
196
320
|
}
|
|
197
321
|
catch (err) {
|
|
198
322
|
await chosen.peeked?.close();
|
|
199
|
-
throw err;
|
|
323
|
+
throw inGraph(chosen.sniff === null ? notReadableAs(err, importer.format, options.filename) : err, builders.length - 1);
|
|
200
324
|
}
|
|
201
|
-
return reports.map((report, i) =>
|
|
325
|
+
return reports.map((report, i) => {
|
|
326
|
+
try {
|
|
327
|
+
return result(chosen, builders[i], report, options);
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
throw inGraph(err, i);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
202
333
|
}
|
|
203
334
|
/**
|
|
204
335
|
* The graphs of an input that can hold several, without importing them (a Cytoscape session's
|
|
@@ -210,11 +341,12 @@ export class FormatRegistry {
|
|
|
210
341
|
* list its graphs (importGraph() then reads the first)
|
|
211
342
|
*/
|
|
212
343
|
async listGraphs(input, options = {}) {
|
|
213
|
-
const
|
|
344
|
+
const opts = options;
|
|
345
|
+
const chosen = await this.choose(input, opts);
|
|
214
346
|
try {
|
|
215
347
|
return chosen.importer.listGraphs === undefined
|
|
216
348
|
? null
|
|
217
|
-
: await chosen.importer.listGraphs(chosen.source, importerOptions(
|
|
349
|
+
: await chosen.importer.listGraphs(chosen.source, importerOptions(opts));
|
|
218
350
|
}
|
|
219
351
|
finally {
|
|
220
352
|
await chosen.peeked?.close();
|
|
@@ -222,25 +354,69 @@ export class FormatRegistry {
|
|
|
222
354
|
}
|
|
223
355
|
/**
|
|
224
356
|
* The importer for an input: the named format, or the sniffed one.
|
|
225
|
-
* @param
|
|
357
|
+
* @param rawInput - the input as the caller passed it
|
|
226
358
|
* @param options - the importGraph options
|
|
227
359
|
* @returns the importer, the sniff, and the input to read (replayed for a stream)
|
|
228
360
|
*/
|
|
229
|
-
async choose(
|
|
361
|
+
async choose(rawInput, options) {
|
|
362
|
+
const input = normalizeInput(rawInput);
|
|
230
363
|
const requested = options.format ?? "auto";
|
|
231
364
|
if (requested !== "auto") {
|
|
232
|
-
return { importer: this.importer(requested), sniff: null, source: input, peeked: null };
|
|
365
|
+
return { importer: this.importer(requested), sniff: null, source: input, peeked: null, warnings: [] };
|
|
366
|
+
}
|
|
367
|
+
// the options are checked before the input is touched, so a bad option never leaves a
|
|
368
|
+
// peeked stream locked; the importer resolves them again with its own defaults
|
|
369
|
+
const common = resolveImportOptions(options, { ids: "keep", defaultDirected: true, weightFrom: null });
|
|
370
|
+
maxEmptyCellsOption(options.maxEmptyCells);
|
|
371
|
+
const peeked = await peekHead(input, SNIFF_HEAD_BYTES, common.signal);
|
|
372
|
+
// the head is sniffed as the importer will read it: in the caller's encoding when given
|
|
373
|
+
const head = common.encoding !== null && peeked.head instanceof Uint8Array
|
|
374
|
+
? new TextDecoder(common.encoding).decode(peeked.head, { stream: true })
|
|
375
|
+
: peeked.head;
|
|
376
|
+
let sniff;
|
|
377
|
+
const failures = new ImportReportBuilder("unknown", 0);
|
|
378
|
+
const warnings = [];
|
|
379
|
+
try {
|
|
380
|
+
const hints = { filename: options.filename, mimeType: options.mimeType, head };
|
|
381
|
+
const ranked = rankFormats(hints, this.importerMap.values(), (format, err) => {
|
|
382
|
+
warnings.push(failures.warning("unsupported", SNIFF_FAILED_CODE, `the ${format} importer's sniff() threw (${messageOf(err)}); it was treated as not recognizing the input`, { element: format }));
|
|
383
|
+
});
|
|
384
|
+
sniff = ranked.length > 0 ? ranked[0] : null;
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
await peeked.close();
|
|
388
|
+
throw err;
|
|
389
|
+
}
|
|
390
|
+
// a known non-graph file is refused whatever its name says, unless an importer claims its
|
|
391
|
+
// content (a Cytoscape session is a zip); an HTML page always (the XML sniffers would
|
|
392
|
+
// otherwise claim it by its markup)
|
|
393
|
+
const foreign = foreignKind(peeked.head);
|
|
394
|
+
const claimed = sniff !== null && sniff.content > 0 && !(foreign?.startsWith("an HTML") ?? false);
|
|
395
|
+
if (sniff === null || (foreign !== null && !claimed)) {
|
|
396
|
+
await peeked.close();
|
|
397
|
+
return this.refuseUndetected(failures, head, foreign, options);
|
|
233
398
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
399
|
+
return { importer: this.importer(sniff.format), sniff, source: peeked.input, peeked, warnings };
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Refuse an input no importer recognizes: empty, or not in a known graph format.
|
|
403
|
+
* @param failures - the report the error is recorded in
|
|
404
|
+
* @param head - the head, as sniffed
|
|
405
|
+
* @param foreign - what known non-graph file the head is, or null
|
|
406
|
+
* @param options - the importGraph options
|
|
407
|
+
* @returns never: always throws
|
|
408
|
+
*/
|
|
409
|
+
refuseUndetected(failures, head, foreign, options) {
|
|
410
|
+
const text = typeof head === "string" ? head : new TextDecoder().decode(head ?? new Uint8Array(0));
|
|
411
|
+
if (text.trim() === "" && text.length < SNIFF_HEAD_BYTES) {
|
|
412
|
+
// nothing to detect: say the input is empty, as a named format would
|
|
413
|
+
return failures.fail(EMPTY_INPUT_CODE, text === "" ? "the input is empty" : "the input holds only whitespace");
|
|
239
414
|
}
|
|
240
|
-
|
|
415
|
+
const what = foreign === null ? "" : `: it is ${foreign}`;
|
|
416
|
+
return failures.fail(UNKNOWN_FORMAT_CODE, `the input is not in a graph format graph-io recognizes${describeHints(options)}${what}; if you know its format, pass it as the format option`, undefined, { formats: this.formats() });
|
|
241
417
|
}
|
|
242
418
|
/**
|
|
243
|
-
* Write a snapshot in a format, as UTF-8 chunks
|
|
419
|
+
* Write a snapshot in a format, as UTF-8 chunks.
|
|
244
420
|
* @param snapshot - the snapshot
|
|
245
421
|
* @param format - the format name
|
|
246
422
|
* @param options - the exporter's common and format-specific options
|
|
@@ -259,6 +435,33 @@ export class FormatRegistry {
|
|
|
259
435
|
async exportGraphToString(snapshot, format, options) {
|
|
260
436
|
return this.exporter(format).exportToString(snapshot, options);
|
|
261
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* Write a snapshot in a format as one buffer of UTF-8 bytes; see the top-level
|
|
440
|
+
* exportGraphToBytes().
|
|
441
|
+
* @param snapshot - the graph to write
|
|
442
|
+
* @param format - a format name where `canExport` is true
|
|
443
|
+
* @param options - sanitizeIds, onMixedDirection and the format's own options
|
|
444
|
+
* @returns the encoded file
|
|
445
|
+
*/
|
|
446
|
+
exportGraphToBytes(snapshot, format, options) {
|
|
447
|
+
return collectBytes(this.exportGraph(snapshot, format, options));
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Write a snapshot in a format as a Blob typed with the format's first MIME type; see the
|
|
451
|
+
* top-level exportGraphToBlob().
|
|
452
|
+
* @param snapshot - the graph to write
|
|
453
|
+
* @param format - a format name where `canExport` is true
|
|
454
|
+
* @param options - sanitizeIds, onMixedDirection and the format's own options
|
|
455
|
+
* @returns the file as a Blob
|
|
456
|
+
*/
|
|
457
|
+
async exportGraphToBlob(snapshot, format, options) {
|
|
458
|
+
const parts = [];
|
|
459
|
+
for await (const chunk of this.exportGraph(snapshot, format, options)) {
|
|
460
|
+
parts.push(chunk);
|
|
461
|
+
}
|
|
462
|
+
const info = this.listFormats().find((f) => f.format === format);
|
|
463
|
+
return new Blob(parts, { type: info?.mimeTypes[0] ?? "application/octet-stream" });
|
|
464
|
+
}
|
|
262
465
|
/**
|
|
263
466
|
* What exporting a snapshot in a format would lose, without writing anything.
|
|
264
467
|
* @param snapshot - the snapshot
|
|
@@ -267,12 +470,94 @@ export class FormatRegistry {
|
|
|
267
470
|
* @returns the loss notes, empty when the export is exact
|
|
268
471
|
*/
|
|
269
472
|
checkExport(snapshot, format, options) {
|
|
270
|
-
|
|
473
|
+
const notes = this.exporter(format).check(snapshot, options);
|
|
474
|
+
const unknown = this.unknownOptions(options);
|
|
475
|
+
if (unknown.length === 0) {
|
|
476
|
+
return notes;
|
|
477
|
+
}
|
|
478
|
+
return Object.freeze([
|
|
479
|
+
...notes,
|
|
480
|
+
...unknown.map((name) => Object.freeze({
|
|
481
|
+
code: UNKNOWN_OPTION_CODE,
|
|
482
|
+
message: unknownOptionText(name),
|
|
483
|
+
column: null,
|
|
484
|
+
count: null,
|
|
485
|
+
})),
|
|
486
|
+
]);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* The option names no registered format and no load or save function takes (misspellings), or none while a
|
|
490
|
+
* registered format does not list its options.
|
|
491
|
+
* @param options - the caller's options
|
|
492
|
+
* @returns the unknown names
|
|
493
|
+
*/
|
|
494
|
+
unknownOptions(options) {
|
|
495
|
+
if (options === null || options === undefined) {
|
|
496
|
+
return [];
|
|
497
|
+
}
|
|
498
|
+
const known = new Set(FUNCTION_OPTIONS);
|
|
499
|
+
for (const plugin of [...this.importerMap.values(), ...this.exporterMap.values()]) {
|
|
500
|
+
if (plugin.options === undefined) {
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
503
|
+
for (const name of plugin.options) {
|
|
504
|
+
known.add(name);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return Object.entries(options)
|
|
508
|
+
.filter(([name, value]) => value !== undefined && !known.has(name))
|
|
509
|
+
.map(([name]) => name);
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Browser only: write a graph in a format and have the browser save it as a file; see the
|
|
513
|
+
* top-level downloadGraph(). Use this method on a registry from createRegistry() to download
|
|
514
|
+
* without bundling every built-in format.
|
|
515
|
+
* @param snapshot - the graph to write
|
|
516
|
+
* @param format - a format name from listFormats() where `canExport` is true
|
|
517
|
+
* @param options - the export options, plus `filename`
|
|
518
|
+
* @returns resolves once the download has been handed to the browser
|
|
519
|
+
*/
|
|
520
|
+
async downloadGraph(snapshot, format, options = {}) {
|
|
521
|
+
if (typeof document === "undefined") {
|
|
522
|
+
throw new GraphFormatError("E_UNSUPPORTED", "downloadGraph() needs a browser document; use exportGraphToBytes() and write the bytes yourself");
|
|
523
|
+
}
|
|
524
|
+
const { filename, ...rest } = options;
|
|
525
|
+
const blob = await this.exportGraphToBlob(snapshot, format, rest);
|
|
526
|
+
const extension = this.listFormats().find((f) => f.format === format)?.extensions[0] ?? `.${format}`;
|
|
527
|
+
const href = URL.createObjectURL(blob);
|
|
528
|
+
const anchor = document.createElement("a");
|
|
529
|
+
anchor.href = href;
|
|
530
|
+
anchor.download = filename ?? `graph${extension}`;
|
|
531
|
+
anchor.click();
|
|
532
|
+
// Safari cancels the download when the URL is revoked in the same task
|
|
533
|
+
setTimeout(() => {
|
|
534
|
+
URL.revokeObjectURL(href);
|
|
535
|
+
}, 0);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Every format this registry can read or write, in registration order; see the top-level
|
|
539
|
+
* listFormats().
|
|
540
|
+
* @returns one entry per format
|
|
541
|
+
*/
|
|
542
|
+
listFormats() {
|
|
543
|
+
return this.formats().map((format) => {
|
|
544
|
+
const importer = this.importerMap.get(format);
|
|
545
|
+
const exporter = this.exporterMap.get(format);
|
|
546
|
+
return Object.freeze({
|
|
547
|
+
format,
|
|
548
|
+
extensions: importer?.extensions ?? exporter?.extensions ?? [],
|
|
549
|
+
mimeTypes: importer?.mimeTypes ?? exporter?.mimeTypes ?? [],
|
|
550
|
+
canImport: importer !== undefined,
|
|
551
|
+
canExport: exporter !== undefined,
|
|
552
|
+
capabilities: exporter?.capabilities ?? null,
|
|
553
|
+
});
|
|
554
|
+
});
|
|
271
555
|
}
|
|
272
556
|
}
|
|
273
557
|
/**
|
|
274
|
-
* A registry holding
|
|
558
|
+
* A new registry holding every built-in importer and exporter, in the order of GRAPH_FORMATS.
|
|
275
559
|
* @returns a new registry
|
|
560
|
+
* @category Formats and detection
|
|
276
561
|
*/
|
|
277
562
|
export function createRegistry() {
|
|
278
563
|
return new FormatRegistry()
|
|
@@ -297,77 +582,355 @@ export function createRegistry() {
|
|
|
297
582
|
.registerImporter(cx2Importer)
|
|
298
583
|
.registerExporter(cx2Exporter)
|
|
299
584
|
.registerImporter(cxImporter)
|
|
585
|
+
.registerExporter(cxExporter)
|
|
300
586
|
.registerImporter(oboImporter)
|
|
301
|
-
.
|
|
587
|
+
.registerExporter(oboExporter)
|
|
588
|
+
.registerImporter(cysImporter)
|
|
589
|
+
.registerExporter(cysExporter);
|
|
302
590
|
}
|
|
303
|
-
/** The default registry: every built-in format. */
|
|
304
|
-
export const registry = createRegistry();
|
|
305
591
|
/**
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
592
|
+
* The default registry: every built-in format.
|
|
593
|
+
* @category Formats and detection
|
|
594
|
+
*/
|
|
595
|
+
export const registry = /* @__PURE__ */ createRegistry();
|
|
596
|
+
/**
|
|
597
|
+
* Read a graph from a string, bytes or a stream. The format is the one you name in `format`, else
|
|
598
|
+
* it is detected from `filename`, `mimeType` and the first bytes; an input that matches no format
|
|
599
|
+
* fails with E_UNKNOWN_FORMAT. From a file that holds several graphs it reads the one `graphIndex`
|
|
600
|
+
* or `graphName` chooses, else the first, with a W_MULTIPLE_GRAPHS warning. For a File or Blob use
|
|
601
|
+
* loadFromFile(), for a URL loadFromUrl().
|
|
602
|
+
* @param input - the whole text, the bytes (a Uint8Array or a Node Buffer), a ReadableStream of
|
|
603
|
+
* bytes, or an async iterable of text or byte chunks (a Node read stream)
|
|
604
|
+
* @param options - the format, format hints, the options every importer takes and the format's own
|
|
605
|
+
* options
|
|
606
|
+
* @returns the graph (`snapshot`), the format it was read as, and the import report
|
|
607
|
+
* @throws ImportError when the input could not be read; `err.issue?.code` says why
|
|
608
|
+
* @throws GraphFormatError with code E_UNSUPPORTED for a `format` no importer is registered for, or
|
|
609
|
+
* an option value that is not allowed (`err.details.option` names it)
|
|
610
|
+
* @throws the signal's reason when `signal` aborts
|
|
611
|
+
* @category Loading
|
|
311
612
|
*/
|
|
312
613
|
export function importGraph(input, options) {
|
|
313
614
|
return registry.importGraph(input, options);
|
|
314
615
|
}
|
|
315
616
|
/**
|
|
316
|
-
* Read every graph of
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
* @
|
|
617
|
+
* Read every graph of a file that can hold several (DOT, GML, a Pajek project, a JSON Graph Format
|
|
618
|
+
* or OBO Graphs document, CX, XGMML, a Cytoscape session). A format that holds one graph per file
|
|
619
|
+
* gives one result. Options and errors are the same as importGraph()'s.
|
|
620
|
+
* @param input - the text, bytes or stream to read
|
|
621
|
+
* @param options - the same options as importGraph()
|
|
622
|
+
* @returns one result per graph, in file order
|
|
623
|
+
* @category Loading
|
|
320
624
|
*/
|
|
321
625
|
export function importAllGraphs(input, options) {
|
|
322
626
|
return registry.importAllGraphs(input, options);
|
|
323
627
|
}
|
|
324
628
|
/**
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
629
|
+
* List the graphs of a file without reading them: each one's `index`, `name`, and node and edge
|
|
630
|
+
* counts when the file states them. Pass the index or name to importGraph() as `graphIndex` or
|
|
631
|
+
* `graphName`. The counts come from the file and can differ from what an import reads, for example
|
|
632
|
+
* when the import adds edges the listing does not count. JSON, CX, XGMML and Cytoscape sessions can
|
|
633
|
+
* list their graphs; for DOT, GML and Pajek it returns null, and importAllGraphs() is the way to see
|
|
634
|
+
* them.
|
|
635
|
+
* @param input - the text, bytes or stream to read
|
|
636
|
+
* @param options - the same options as importGraph()
|
|
637
|
+
* @returns one listing per graph, in file order; null when the format cannot list its graphs
|
|
638
|
+
* @category Loading
|
|
329
639
|
*/
|
|
330
640
|
export function listGraphs(input, options) {
|
|
331
641
|
return registry.listGraphs(input, options);
|
|
332
642
|
}
|
|
333
643
|
/**
|
|
334
|
-
* Write a
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
* @param
|
|
338
|
-
* @
|
|
644
|
+
* Write a graph in a format as a stream of UTF-8 byte chunks, for a graph too large to hold as one
|
|
645
|
+
* file in memory. In Node, pipe it to a file with `pipeline(exportGraph(...), createWriteStream(path))`.
|
|
646
|
+
* Options and errors are the same as exportGraphToBytes()'s.
|
|
647
|
+
* @param snapshot - the graph to write
|
|
648
|
+
* @param format - a format name from listFormats() where `canExport` is true
|
|
649
|
+
* @param options - sanitizeIds, onMixedDirection and the format's own options
|
|
650
|
+
* @returns the file's bytes, chunk by chunk
|
|
651
|
+
* @category Saving
|
|
339
652
|
*/
|
|
340
653
|
export function exportGraph(snapshot, format, options) {
|
|
341
654
|
return registry.exportGraph(snapshot, format, options);
|
|
342
655
|
}
|
|
343
656
|
/**
|
|
344
|
-
* Write a
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
* @param
|
|
348
|
-
* @
|
|
657
|
+
* Write a graph in a format and return the file as one string. A binary format (a Cytoscape
|
|
658
|
+
* session) cannot be a string and fails with E_UNSUPPORTED; use exportGraphToBytes() for it. Options
|
|
659
|
+
* and errors are the same as exportGraphToBytes()'s.
|
|
660
|
+
* @param snapshot - the graph to write
|
|
661
|
+
* @param format - a format name from listFormats() where `canExport` is true
|
|
662
|
+
* @param options - sanitizeIds, onMixedDirection and the format's own options
|
|
663
|
+
* @returns the whole file
|
|
664
|
+
* @category Saving
|
|
349
665
|
*/
|
|
350
666
|
export async function exportGraphToString(snapshot, format, options) {
|
|
351
667
|
return registry.exportGraphToString(snapshot, format, options);
|
|
352
668
|
}
|
|
353
669
|
/**
|
|
354
|
-
* What
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
* @
|
|
670
|
+
* What saving a graph in a format would lose, without writing anything. Pass the same options you
|
|
671
|
+
* will pass to the save. Each note has a stable `code`: "E_" means the save would throw, "W_" means
|
|
672
|
+
* the file is written but that part does not read back the same. An empty array means the file
|
|
673
|
+
* reads back as the same graph. Runs synchronously.
|
|
674
|
+
* @param snapshot - the graph to check
|
|
675
|
+
* @param format - a format name from listFormats() where `canExport` is true
|
|
676
|
+
* @param options - the options you will save with
|
|
677
|
+
* @returns the loss notes, empty when the save is exact
|
|
678
|
+
* @throws GraphFormatError with code E_UNSUPPORTED when no exporter is registered for `format`, or
|
|
679
|
+
* when an option value is not allowed (`err.details.option` names it)
|
|
680
|
+
* @category Saving
|
|
359
681
|
*/
|
|
360
682
|
export function checkExport(snapshot, format, options) {
|
|
361
683
|
return registry.checkExport(snapshot, format, options);
|
|
362
684
|
}
|
|
363
685
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
686
|
+
* Fetch a graph file from a URL and load it. The file is streamed into the importer as it
|
|
687
|
+
* downloads, so a large file is not held in memory twice. When you do not name a `format`, it is
|
|
688
|
+
* worked out from the URL's file name, the response's Content-Type and the first bytes of the
|
|
689
|
+
* file. Your own `filename` or `mimeType` option takes the place of the hint from the URL or the
|
|
690
|
+
* response. Every other option goes to importGraph() unchanged, including format-specific ones
|
|
691
|
+
* such as `delimiter` or `dialect`, and `graphIndex` / `graphName` for files that hold several
|
|
692
|
+
* graphs. The result is the same as importGraph()'s. A successful load can still have skipped
|
|
693
|
+
* elements; check `report.errorCount` and `report.warningCount`, or pass `errorLimit: 0` to make
|
|
694
|
+
* the first error fatal. Works wherever fetch exists: browsers, Node 18 and later, Deno, Bun and
|
|
695
|
+
* workers.
|
|
696
|
+
* @param url - an absolute URL, or in a browser one relative to the page
|
|
697
|
+
* @param options - import options, plus `request` for fetch
|
|
698
|
+
* @returns the graph, the format it was read as, and the import report
|
|
699
|
+
* @throws ImportError when the file could not be loaded; `err.issue?.code` is "E_FETCH" for a
|
|
700
|
+
* network failure, a CORS refusal or a status outside 200-299 (`err.details.url`,
|
|
701
|
+
* `err.details.status` -- null for a network failure -- and `err.details.cause`, fetch's own
|
|
702
|
+
* error), "E_UNKNOWN_FORMAT" when no format recognizes the file (pass `format`), or the code of
|
|
703
|
+
* the parse error
|
|
704
|
+
* @throws GraphFormatError with code E_UNSUPPORTED when `format` names no registered importer
|
|
705
|
+
* @throws the signal's reason when `signal` aborts
|
|
706
|
+
* @example
|
|
707
|
+
* ```ts
|
|
708
|
+
* import { GraphFormatError, loadFromUrl } from "@graphty/graph-io";
|
|
709
|
+
*
|
|
710
|
+
* try {
|
|
711
|
+
* const { snapshot, format } = await loadFromUrl("https://example.com/data/karate.gml");
|
|
712
|
+
* console.log(`${format}: ${snapshot.nodeCount} nodes, ${snapshot.edgeCount} edges`);
|
|
713
|
+
* } catch (err) {
|
|
714
|
+
* if (err instanceof GraphFormatError) {
|
|
715
|
+
* console.error(err.message);
|
|
716
|
+
* } else {
|
|
717
|
+
* throw err;
|
|
718
|
+
* }
|
|
719
|
+
* }
|
|
720
|
+
* ```
|
|
721
|
+
* @category Loading
|
|
722
|
+
*/
|
|
723
|
+
export function loadFromUrl(url, options) {
|
|
724
|
+
return registry.loadFromUrl(url, options);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Load a graph from a File or Blob: a file the user picked in an `<input type="file">`, a file
|
|
728
|
+
* dropped on the page, a Blob you built, or in Node a Blob from `fs.openAsBlob(path)`. The content
|
|
729
|
+
* is streamed into the importer. A File's `name` and the Blob's `type` are used as format hints
|
|
730
|
+
* unless you pass `filename` or `mimeType` yourself. Options, result and errors are the same as
|
|
731
|
+
* loadFromUrl()'s, without E_FETCH.
|
|
732
|
+
* @param file - the File or Blob to read
|
|
733
|
+
* @param options - the same options as importGraph()
|
|
734
|
+
* @returns the graph, the format it was read as, and the import report
|
|
735
|
+
* @throws ImportError when the file could not be loaded (`err.issue?.code` E_UNKNOWN_FORMAT or a parse error)
|
|
736
|
+
* @throws GraphFormatError with code E_UNSUPPORTED when `format` names no registered importer
|
|
737
|
+
* @throws the signal's reason when `signal` aborts
|
|
738
|
+
* @example
|
|
739
|
+
* ```ts
|
|
740
|
+
* input.addEventListener("change", async () => {
|
|
741
|
+
* const file = input.files?.[0];
|
|
742
|
+
* if (file) {
|
|
743
|
+
* const { snapshot, report } = await loadFromFile(file);
|
|
744
|
+
* console.log(`${snapshot.nodeCount} nodes, ${report.errorCount} skipped`);
|
|
745
|
+
* }
|
|
746
|
+
* });
|
|
747
|
+
* ```
|
|
748
|
+
* @category Loading
|
|
749
|
+
*/
|
|
750
|
+
export function loadFromFile(file, options) {
|
|
751
|
+
return registry.loadFromFile(file, options);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Write a graph in a format and return the whole file as UTF-8 bytes, ready for fs.writeFile, a
|
|
755
|
+
* fetch body or a zip entry. Prefer this over exportGraphToString() when the result goes to a file
|
|
756
|
+
* or the network. To stream a very large graph, use exportGraph(). To find out beforehand what the
|
|
757
|
+
* file will not keep, call checkExport() with the same format and the same options object: every
|
|
758
|
+
* "E_" note it returns makes this call throw, and every "W_" note describes a loss this call
|
|
759
|
+
* accepts. Most "E_" notes go away with `sanitizeIds: "mangle"` (rewrite ids the format cannot
|
|
760
|
+
* hold) or `onMixedDirection: "directed"` / `"undirected"`. The format's own options (the CSV
|
|
761
|
+
* `table` and `dialect`, the GEXF `version`, ...) go in the same object.
|
|
762
|
+
* @param snapshot - the graph to write
|
|
763
|
+
* @param format - a format name from listFormats() where `canExport` is true
|
|
764
|
+
* @param options - sanitizeIds, onMixedDirection and the format's own options
|
|
765
|
+
* @returns the encoded file
|
|
766
|
+
* @throws GraphFormatError with code E_UNSUPPORTED when no exporter is registered for `format`
|
|
767
|
+
* @throws GraphFormatError when the graph cannot be written in this format under these options
|
|
768
|
+
* (checkExport() returns an "E_" note for the same call). The thrown code names the kind of
|
|
769
|
+
* failure rather than the note: E_INVALID_ID for ids (the E_ID_CHARSET and E_ID_TEXT_COLLISION
|
|
770
|
+
* notes), E_DIRECTED for direction (E_MIXED_DIRECTION), and E_COLUMN_TYPE or E_UNSUPPORTED for a
|
|
771
|
+
* value the format cannot write; each note's entry on the codes page says which
|
|
772
|
+
* @throws GraphFormatError with code E_UNSUPPORTED for an option value that is not allowed
|
|
773
|
+
* (`err.details.option` names it)
|
|
774
|
+
* @example
|
|
775
|
+
* ```ts
|
|
776
|
+
* import { writeFile } from "node:fs/promises";
|
|
777
|
+
*
|
|
778
|
+
* await writeFile("out.gexf", await exportGraphToBytes(snapshot, "gexf"));
|
|
779
|
+
* ```
|
|
780
|
+
* @category Saving
|
|
781
|
+
*/
|
|
782
|
+
export function exportGraphToBytes(snapshot, format, options) {
|
|
783
|
+
return registry.exportGraphToBytes(snapshot, format, options);
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Write a graph in a format and return it as a Blob, for uploading with fetch or FormData or for
|
|
787
|
+
* offering as a download. The Blob's `type` is the first entry of the format's `mimeTypes` in
|
|
788
|
+
* listFormats(), or application/octet-stream when the format lists none. The exporter's chunks
|
|
789
|
+
* are kept as they are and never joined into one string. Options and errors are the same as
|
|
790
|
+
* exportGraphToBytes().
|
|
791
|
+
* @param snapshot - the graph to write
|
|
792
|
+
* @param format - a format name from listFormats() where `canExport` is true
|
|
793
|
+
* @param options - sanitizeIds, onMixedDirection and the format's own options
|
|
794
|
+
* @returns the file as a Blob
|
|
795
|
+
* @throws the same errors as exportGraphToBytes()
|
|
796
|
+
* @example
|
|
797
|
+
* ```ts
|
|
798
|
+
* const body = new FormData();
|
|
799
|
+
* body.append("file", await exportGraphToBlob(snapshot, "graphml"), "graph.graphml");
|
|
800
|
+
* await fetch("/api/graphs", { method: "POST", body });
|
|
801
|
+
* ```
|
|
802
|
+
* @category Saving
|
|
803
|
+
*/
|
|
804
|
+
export function exportGraphToBlob(snapshot, format, options) {
|
|
805
|
+
return registry.exportGraphToBlob(snapshot, format, options);
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Browser only: write a graph in a format and have the browser save it as a file, the same as
|
|
809
|
+
* clicking a download link. Call it from a click handler; awaiting other work first is fine. The
|
|
810
|
+
* promise resolves once the file has been handed to the browser. There is no way to learn
|
|
811
|
+
* whether the user then canceled the save. Calling it where there is no `document` (Node, a
|
|
812
|
+
* worker) throws instead of silently doing nothing; importing it is safe everywhere. Formats you
|
|
813
|
+
* registered with `registry.registerExporter()` can be downloaded too.
|
|
814
|
+
* @param snapshot - the graph to write
|
|
815
|
+
* @param format - a format name from listFormats() where `canExport` is true
|
|
816
|
+
* @param options - the export options, plus `filename`
|
|
817
|
+
* @returns resolves once the download has been handed to the browser
|
|
818
|
+
* @throws GraphFormatError with code E_UNSUPPORTED when called without a DOM `document`
|
|
819
|
+
* @throws the same errors as exportGraphToBytes()
|
|
820
|
+
* @example
|
|
821
|
+
* ```ts
|
|
822
|
+
* saveButton.addEventListener("click", () => {
|
|
823
|
+
* void downloadGraph(snapshot, "graphml", { filename: "network.graphml" });
|
|
824
|
+
* });
|
|
825
|
+
* ```
|
|
826
|
+
* @category Saving
|
|
827
|
+
*/
|
|
828
|
+
export async function downloadGraph(snapshot, format, options = {}) {
|
|
829
|
+
return registry.downloadGraph(snapshot, format, options);
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Every format the default registry can read or write, including any you registered yourself:
|
|
833
|
+
* the built-in formats in the order of GRAPH_FORMATS (the order format detection prefers them),
|
|
834
|
+
* then yours in registration order. Use it to fill a "Save as" menu (the entries with
|
|
835
|
+
* `canExport`), a file input's `accept` attribute (the `extensions` of the entries with
|
|
836
|
+
* `canImport`), or to pick the output format from a file name.
|
|
837
|
+
* @returns one entry per format
|
|
838
|
+
* @example
|
|
839
|
+
* ```ts
|
|
840
|
+
* input.accept = listFormats().filter((f) => f.canImport).flatMap((f) => f.extensions).join(",");
|
|
841
|
+
* const target = listFormats().find((f) => f.canExport && f.extensions.includes(extensionOf(path) ?? ""));
|
|
842
|
+
* ```
|
|
843
|
+
* @category Formats and detection
|
|
844
|
+
*/
|
|
845
|
+
export function listFormats() {
|
|
846
|
+
return registry.listFormats();
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Detect a file's format without reading it, from what you know about it: a `filename` (only the
|
|
850
|
+
* extension counts), a `mimeType`, and `head`, the file's first bytes or characters (up to
|
|
851
|
+
* SNIFF_HEAD_BYTES; pass `bytes.subarray(0, SNIFF_HEAD_BYTES)`). It uses every format of the default
|
|
852
|
+
* registry, including the ones you registered. For every candidate, best first, call
|
|
853
|
+
* `registry.sniffAll(hints)`.
|
|
854
|
+
* @param hints - `{ filename, mimeType, head }`, any of them
|
|
855
|
+
* @returns the best candidate (`format`, `confidence` and what matched), or null when no format
|
|
856
|
+
* claims the input
|
|
857
|
+
* @example
|
|
858
|
+
* ```ts
|
|
859
|
+
* sniff({ filename: "graph.xml", head: "<?xml version=\"1.0\"?><gexf" })?.format; // "gexf"
|
|
860
|
+
* ```
|
|
861
|
+
* @category Formats and detection
|
|
367
862
|
*/
|
|
368
863
|
export function sniff(hints) {
|
|
369
864
|
return registry.sniff(hints);
|
|
370
865
|
}
|
|
866
|
+
/**
|
|
867
|
+
* Abort a loadFromUrl() whose fetch failed with an E_FETCH ImportError.
|
|
868
|
+
* @param options - the import options (for the format name on the report)
|
|
869
|
+
* @param url - the URL
|
|
870
|
+
* @param message - the message
|
|
871
|
+
* @param status - the HTTP status, or null for a network failure
|
|
872
|
+
* @param cause - fetch's own error, or null
|
|
873
|
+
* @returns never; always throws
|
|
874
|
+
*/
|
|
875
|
+
function fetchFailed(options, url, message, status, cause) {
|
|
876
|
+
const format = options.format === undefined || options.format === "auto" ? "unknown" : options.format;
|
|
877
|
+
return new ImportReportBuilder(format, 0).fail(FETCH_CODE, message, undefined, { url: String(url), status, cause });
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* The error for an input refused by the format the caller named before any element was read: the
|
|
881
|
+
* parser's message ("line 1: text outside the root element") prefixed with the file and the format,
|
|
882
|
+
* since the likely cause is a file of another kind, not a damaged one.
|
|
883
|
+
* @param err - what the importer threw
|
|
884
|
+
* @param format - the format named in `format`
|
|
885
|
+
* @param filename - the file name hint, if any
|
|
886
|
+
* @returns the error to throw
|
|
887
|
+
*/
|
|
888
|
+
function notReadableAs(err, format, filename) {
|
|
889
|
+
if (!(err instanceof ImportError) ||
|
|
890
|
+
err.issue?.category !== "parse-error" ||
|
|
891
|
+
err.report.errorCount > 1 ||
|
|
892
|
+
err.report.counts.nodes + err.report.counts.edges > 0) {
|
|
893
|
+
return err;
|
|
894
|
+
}
|
|
895
|
+
const what = filename === undefined || filename === null ? "the input" : JSON.stringify(filename);
|
|
896
|
+
return new ImportError(`${what} could not be read as ${format}: ${err.message}`, err.report, err.details);
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Why fetch() rejected, in words that point at the fix: a relative URL with no page to resolve it
|
|
900
|
+
* against (Node), a file: URL fetch() does not read, or a network error, which in a browser
|
|
901
|
+
* includes a CORS refusal.
|
|
902
|
+
* @param url - the URL as given
|
|
903
|
+
* @param err - what fetch() threw
|
|
904
|
+
* @returns the reason
|
|
905
|
+
*/
|
|
906
|
+
function fetchErrorText(url, err) {
|
|
907
|
+
let absolute = null;
|
|
908
|
+
try {
|
|
909
|
+
absolute = new URL(url);
|
|
910
|
+
}
|
|
911
|
+
catch {
|
|
912
|
+
// relative: resolvable only against a page's location
|
|
913
|
+
}
|
|
914
|
+
if (absolute === null && globalThis.location === undefined) {
|
|
915
|
+
return "a relative URL needs a web page to resolve against; outside a browser pass an absolute http(s) URL, or read a local file with loadFromFile() or importGraph()";
|
|
916
|
+
}
|
|
917
|
+
if (absolute?.protocol === "file:") {
|
|
918
|
+
return "fetch() cannot read file: URLs here; read a local file with loadFromFile() or importGraph()";
|
|
919
|
+
}
|
|
920
|
+
const cause = err?.cause?.message;
|
|
921
|
+
const detail = typeof cause === "string" && cause !== "" ? ` (${cause})` : "";
|
|
922
|
+
return `the server could not be reached, or (in a browser) it does not allow this page to read the file (CORS)${detail}`;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* The last path segment of a URL, as a format hint.
|
|
926
|
+
* @param url - the URL, absolute or relative
|
|
927
|
+
* @returns the file name, or null when the path ends in "/"
|
|
928
|
+
*/
|
|
929
|
+
function urlFilename(url) {
|
|
930
|
+
const path = new URL(url, "http://localhost/").pathname;
|
|
931
|
+
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
932
|
+
return name === "" ? null : name;
|
|
933
|
+
}
|
|
371
934
|
/**
|
|
372
935
|
* The options handed to the importer: everything but the registry's own keys.
|
|
373
936
|
* @param options - the importGraph options
|
|
@@ -391,6 +954,26 @@ function importerOptions(options) {
|
|
|
391
954
|
*/
|
|
392
955
|
function seededBuilder(options, format) {
|
|
393
956
|
const maxEmptyCells = maxEmptyCellsOption(options.maxEmptyCells);
|
|
957
|
+
try {
|
|
958
|
+
return newBuilder(options, format, maxEmptyCells);
|
|
959
|
+
}
|
|
960
|
+
catch (err) {
|
|
961
|
+
// the builder names a bad value by its field; report it as the option the caller passed
|
|
962
|
+
if (err instanceof GraphFormatError && err.code === "E_UNSUPPORTED" && typeof err.details.field === "string") {
|
|
963
|
+
const option = err.details.field;
|
|
964
|
+
throw new GraphFormatError("E_UNSUPPORTED", `option ${option}: ${JSON.stringify(err.details.found)} is not one of the values it takes`, { option, found: err.details.found });
|
|
965
|
+
}
|
|
966
|
+
throw err;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* The builder of seededBuilder(), before its option errors are reworded.
|
|
971
|
+
* @param options - the importGraph options
|
|
972
|
+
* @param format - the format being read
|
|
973
|
+
* @param maxEmptyCells - the empty-cell budget
|
|
974
|
+
* @returns the builder
|
|
975
|
+
*/
|
|
976
|
+
function newBuilder(options, format, maxEmptyCells) {
|
|
394
977
|
return new CellBudgetBuilder({
|
|
395
978
|
weightDtype: options.weightDtype ?? "f64",
|
|
396
979
|
...options.builder,
|
|
@@ -400,6 +983,20 @@ function seededBuilder(options, format) {
|
|
|
400
983
|
selfLoops: options.selfLoops ?? "keep",
|
|
401
984
|
}, format, maxEmptyCells);
|
|
402
985
|
}
|
|
986
|
+
/**
|
|
987
|
+
* Name the graph an importAllGraphs() failure belongs to: the report is that graph's own, so the
|
|
988
|
+
* message is prefixed with its index and `details.graphIndex` set, for every graph including the
|
|
989
|
+
* first. Anything other than an ImportError raised while a graph was being read passes unchanged.
|
|
990
|
+
* @param err - the error thrown while reading or freezing a graph
|
|
991
|
+
* @param index - the index of that graph, -1 before the first graph began
|
|
992
|
+
* @returns the error to throw
|
|
993
|
+
*/
|
|
994
|
+
function inGraph(err, index) {
|
|
995
|
+
if (!(err instanceof ImportError) || index < 0) {
|
|
996
|
+
return err;
|
|
997
|
+
}
|
|
998
|
+
return new ImportError(`graph ${index}: ${err.message}`, err.report, { ...err.details, graphIndex: index });
|
|
999
|
+
}
|
|
403
1000
|
/**
|
|
404
1001
|
* Freeze an imported builder into an importGraph result.
|
|
405
1002
|
* @param chosen - the importer and its sniff
|
|
@@ -409,15 +1006,125 @@ function seededBuilder(options, format) {
|
|
|
409
1006
|
* @returns the frozen result
|
|
410
1007
|
*/
|
|
411
1008
|
function result(chosen, builder, report, options) {
|
|
412
|
-
|
|
413
|
-
|
|
1009
|
+
let frozen;
|
|
1010
|
+
try {
|
|
1011
|
+
frozen = builder.freezeWithReport(options.freeze);
|
|
1012
|
+
}
|
|
1013
|
+
catch (err) {
|
|
1014
|
+
throw freezeFailure(err, report);
|
|
1015
|
+
}
|
|
1016
|
+
const { warnings } = chosen;
|
|
1017
|
+
const result = {
|
|
414
1018
|
format: chosen.importer.format,
|
|
415
1019
|
sniff: chosen.sniff,
|
|
416
1020
|
snapshot: frozen.snapshot,
|
|
417
|
-
report
|
|
418
|
-
|
|
1021
|
+
report: withFreezeWarnings(weightNotFound(warnings.length === 0
|
|
1022
|
+
? report
|
|
1023
|
+
: Object.freeze({
|
|
1024
|
+
...report,
|
|
1025
|
+
issues: Object.freeze([...warnings, ...report.issues]),
|
|
1026
|
+
warningCount: report.warningCount + warnings.length,
|
|
1027
|
+
}), frozen.snapshot, options.weightFrom), frozen.report),
|
|
1028
|
+
};
|
|
1029
|
+
// not enumerable: it holds one entry per edge, and console.log(result) should show the graph, not that
|
|
1030
|
+
Object.defineProperty(result, "freeze", { value: frozen.report, enumerable: false });
|
|
1031
|
+
return Object.freeze(result);
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* The message of a W_UNKNOWN_OPTION warning or note.
|
|
1035
|
+
* @param name - the option name
|
|
1036
|
+
* @returns the message
|
|
1037
|
+
*/
|
|
1038
|
+
function unknownOptionText(name) {
|
|
1039
|
+
return `option ${JSON.stringify(name)} is not an option of any format or of the load and save functions, so it has no effect; check its spelling`;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* The import report with one W_WEIGHT_NOT_FOUND warning when the caller's `weightFrom` named an attribute that gave
|
|
1043
|
+
* no edge a weight, so a name that misses (a shared options object read against GML's `value`) is not silent. A
|
|
1044
|
+
* format that does not read `weightFrom` already reported it as W_OPTION_IGNORED.
|
|
1045
|
+
* @param report - the import's report
|
|
1046
|
+
* @param snapshot - the graph read
|
|
1047
|
+
* @param weightFrom - the caller's option
|
|
1048
|
+
* @returns the report, with the warning added when it applies
|
|
1049
|
+
*/
|
|
1050
|
+
function weightNotFound(report, snapshot, weightFrom) {
|
|
1051
|
+
if (typeof weightFrom !== "string" ||
|
|
1052
|
+
snapshot.edgeCount === 0 ||
|
|
1053
|
+
snapshot.edgeList().weights !== null ||
|
|
1054
|
+
report.issues.some((i) => i.code === OPTION_IGNORED_CODE && i.element === "weightFrom")) {
|
|
1055
|
+
return report;
|
|
1056
|
+
}
|
|
1057
|
+
const issue = Object.freeze({
|
|
1058
|
+
category: "missing-value",
|
|
1059
|
+
severity: "warning",
|
|
1060
|
+
code: WEIGHT_NOT_FOUND_CODE,
|
|
1061
|
+
message: `weightFrom: ${JSON.stringify(weightFrom)} names no attribute an edge has a value for, so the graph has no weights`,
|
|
1062
|
+
line: null,
|
|
1063
|
+
element: "weightFrom",
|
|
1064
|
+
});
|
|
1065
|
+
return Object.freeze({
|
|
1066
|
+
...report,
|
|
1067
|
+
issues: Object.freeze([...report.issues, issue]),
|
|
1068
|
+
warningCount: report.warningCount + 1,
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* The import report with what the builder's policies changed while the graph was built: one
|
|
1073
|
+
* W_EDGES_MERGED warning when `duplicateEdges` merged parallel edges (whatever told them apart, a
|
|
1074
|
+
* relation or a label, is lost) and one W_SELF_LOOPS_DROPPED warning when `selfLoops: "drop"`
|
|
1075
|
+
* removed self-loops, so nothing the caller's options removed goes unreported.
|
|
1076
|
+
* @param report - the import's report
|
|
1077
|
+
* @param freeze - the freeze report
|
|
1078
|
+
* @returns the report, with the warnings added
|
|
1079
|
+
*/
|
|
1080
|
+
function withFreezeWarnings(report, freeze) {
|
|
1081
|
+
const added = [];
|
|
1082
|
+
const warn = (category, code, message) => {
|
|
1083
|
+
added.push(Object.freeze({ category, severity: "warning", code, message, line: null, element: null }));
|
|
1084
|
+
};
|
|
1085
|
+
if (freeze.mergedEdges > 0) {
|
|
1086
|
+
warn("merged", EDGES_MERGED_CODE, `${freeze.mergedEdges} parallel edge${plural(freeze.mergedEdges)} ${agree(freeze.mergedEdges, "was", "were")} merged into one edge per pair by the duplicateEdges policy; whatever told them apart (a relation, a label) is lost`);
|
|
1087
|
+
}
|
|
1088
|
+
if (freeze.droppedSelfLoops > 0) {
|
|
1089
|
+
warn("validation-error", SELF_LOOPS_DROPPED_CODE, `${freeze.droppedSelfLoops} self-loop${plural(freeze.droppedSelfLoops)} ${agree(freeze.droppedSelfLoops, "was", "were")} removed by selfLoops: "drop"`);
|
|
1090
|
+
}
|
|
1091
|
+
if (added.length === 0) {
|
|
1092
|
+
return report;
|
|
1093
|
+
}
|
|
1094
|
+
return Object.freeze({
|
|
1095
|
+
...report,
|
|
1096
|
+
issues: Object.freeze([...report.issues, ...added]),
|
|
1097
|
+
warningCount: report.warningCount + added.length,
|
|
419
1098
|
});
|
|
420
1099
|
}
|
|
1100
|
+
/**
|
|
1101
|
+
* The ImportError of a freeze the builder refused (a `duplicateEdges: "error"` or `selfLoops:
|
|
1102
|
+
* "error"` policy meeting a duplicate edge or a self-loop the file holds): the builder's code
|
|
1103
|
+
* recorded as an error issue on the import's report, so the caller gets the report and the file's
|
|
1104
|
+
* defect rather than a bare GraphFormatError.
|
|
1105
|
+
* @param err - what freezeWithReport() threw
|
|
1106
|
+
* @param report - the import's report
|
|
1107
|
+
* @returns the error to throw (anything that is not a GraphFormatError, unchanged)
|
|
1108
|
+
*/
|
|
1109
|
+
function freezeFailure(err, report) {
|
|
1110
|
+
if (!(err instanceof GraphFormatError) || err instanceof ImportError) {
|
|
1111
|
+
return err;
|
|
1112
|
+
}
|
|
1113
|
+
const issue = Object.freeze({
|
|
1114
|
+
category: "validation-error",
|
|
1115
|
+
severity: "error",
|
|
1116
|
+
code: err.code,
|
|
1117
|
+
message: err.message,
|
|
1118
|
+
line: null,
|
|
1119
|
+
element: null,
|
|
1120
|
+
});
|
|
1121
|
+
const failed = Object.freeze({
|
|
1122
|
+
...report,
|
|
1123
|
+
issues: Object.freeze([...report.issues, issue]),
|
|
1124
|
+
errorCount: report.errorCount + 1,
|
|
1125
|
+
});
|
|
1126
|
+
return new ImportError(`the graph cannot be frozen: ${err.message}`, failed, { code: err.code, ...err.details });
|
|
1127
|
+
}
|
|
421
1128
|
/**
|
|
422
1129
|
* The E_UNSUPPORTED error of a format name nothing is registered under.
|
|
423
1130
|
* @param kind - importer or exporter
|
|
@@ -463,7 +1170,7 @@ async function peekHead(input, bytes, signal) {
|
|
|
463
1170
|
}
|
|
464
1171
|
throwIfAborted(signal);
|
|
465
1172
|
const source = typeof input.getReader === "function"
|
|
466
|
-
? readerIterator(input)
|
|
1173
|
+
? readerIterator(input, signal)
|
|
467
1174
|
: input[Symbol.asyncIterator]();
|
|
468
1175
|
const buffered = [];
|
|
469
1176
|
let size = 0;
|
|
@@ -565,19 +1272,31 @@ function replay(buffered, source, finished) {
|
|
|
565
1272
|
};
|
|
566
1273
|
}
|
|
567
1274
|
/**
|
|
568
|
-
* An async iterator over a ReadableStream's chunks that cancels the stream
|
|
1275
|
+
* An async iterator over a ReadableStream's chunks that cancels the stream (with the signal's
|
|
1276
|
+
* reason) when closed early or when the signal fires during a read, and releases the reader when
|
|
1277
|
+
* the stream ends or errors.
|
|
569
1278
|
* @param stream - the stream
|
|
1279
|
+
* @param signal - the cancellation signal, or null
|
|
570
1280
|
* @returns the iterator
|
|
571
1281
|
*/
|
|
572
|
-
function readerIterator(stream) {
|
|
573
|
-
const reader = stream
|
|
1282
|
+
function readerIterator(stream, signal) {
|
|
1283
|
+
const reader = lockReader(stream);
|
|
574
1284
|
let done = false;
|
|
575
1285
|
return {
|
|
576
1286
|
async next() {
|
|
577
1287
|
if (done) {
|
|
578
1288
|
return { done: true, value: undefined };
|
|
579
1289
|
}
|
|
580
|
-
|
|
1290
|
+
let result;
|
|
1291
|
+
try {
|
|
1292
|
+
result = await abortable(reader.read(), signal);
|
|
1293
|
+
}
|
|
1294
|
+
catch (err) {
|
|
1295
|
+
done = true;
|
|
1296
|
+
await reader.cancel(signal?.reason).catch(() => undefined);
|
|
1297
|
+
reader.releaseLock();
|
|
1298
|
+
throw err;
|
|
1299
|
+
}
|
|
581
1300
|
if (result.done) {
|
|
582
1301
|
done = true;
|
|
583
1302
|
reader.releaseLock();
|
|
@@ -588,7 +1307,7 @@ function readerIterator(stream) {
|
|
|
588
1307
|
async return() {
|
|
589
1308
|
if (!done) {
|
|
590
1309
|
done = true;
|
|
591
|
-
await reader.cancel().catch(() => undefined);
|
|
1310
|
+
await reader.cancel(signal?.reason).catch(() => undefined);
|
|
592
1311
|
reader.releaseLock();
|
|
593
1312
|
}
|
|
594
1313
|
return { done: true, value: undefined };
|