@graphty/graph-io 0.3.25 → 0.3.27
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-DrXtu_96.js +56 -0
- package/dist/chunks/choose-DrXtu_96.js.map +1 -0
- package/dist/chunks/constants-ikG85dT8.js +231 -0
- package/dist/chunks/constants-ikG85dT8.js.map +1 -0
- package/dist/chunks/{escape-B-tkk_Cl.js → escape-D1HPTUrP.js} +390 -99
- package/dist/chunks/escape-D1HPTUrP.js.map +1 -0
- package/dist/chunks/export-CHd4tQE2.js +4333 -0
- package/dist/chunks/export-CHd4tQE2.js.map +1 -0
- package/dist/chunks/{importer-eUREhBGj.js → exporter-D23CKNqF.js} +4969 -3201
- package/dist/chunks/exporter-D23CKNqF.js.map +1 -0
- package/dist/chunks/{exporter-DIeGJXAZ.js → exporter-DfWTgrti.js} +23 -21
- package/dist/chunks/exporter-DfWTgrti.js.map +1 -0
- package/dist/chunks/exporter-DhtW6FxK.js +3465 -0
- package/dist/chunks/exporter-DhtW6FxK.js.map +1 -0
- package/dist/chunks/{exporter-BGrsWamJ.js → exporter-IHv4nkCm.js} +38 -229
- package/dist/chunks/exporter-IHv4nkCm.js.map +1 -0
- package/dist/chunks/{importer-BWY2FFc5.js → importer-B1wmPQjr.js} +255 -77
- package/dist/chunks/importer-B1wmPQjr.js.map +1 -0
- package/dist/chunks/{importer-D_e7e7LX.js → importer-B2W_58pz.js} +399 -146
- package/dist/chunks/importer-B2W_58pz.js.map +1 -0
- package/dist/chunks/importer-BSALvkui.js +2811 -0
- package/dist/chunks/importer-BSALvkui.js.map +1 -0
- package/dist/chunks/{importer-vi3bSdtw.js → importer-BVRE--Un.js} +510 -77
- package/dist/chunks/importer-BVRE--Un.js.map +1 -0
- package/dist/chunks/{importer-DET1tg6s.js → importer-CIAzt-wo.js} +1689 -312
- package/dist/chunks/importer-CIAzt-wo.js.map +1 -0
- package/dist/chunks/{importer-DIrbbnAf.js → importer-DpEW5zeG.js} +585 -226
- package/dist/chunks/importer-DpEW5zeG.js.map +1 -0
- package/dist/chunks/{importer-zGLo8Dg_.js → importer-z_FL4VMb.js} +488 -161
- package/dist/chunks/importer-z_FL4VMb.js.map +1 -0
- package/dist/chunks/json-elements-BAmNBZok.js +1225 -0
- package/dist/chunks/json-elements-BAmNBZok.js.map +1 -0
- package/dist/chunks/ontology-export-Bab4eUA8.js +357 -0
- package/dist/chunks/ontology-export-Bab4eUA8.js.map +1 -0
- package/dist/chunks/{records-dbRkxwaq.js → records-CdlvL4RO.js} +298 -114
- package/dist/chunks/records-CdlvL4RO.js.map +1 -0
- package/dist/chunks/{text-Dr0Ifpag.js → text-DzXjAiTl.js} +29 -11
- package/dist/chunks/text-DzXjAiTl.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 +952 -262
- 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 +344 -87
- 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 +565 -158
- 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 +21 -3
- 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 +39 -22
- 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 +869 -189
- 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 +3 -1
- 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 +402 -85
- 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 +146 -33
- 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 +682 -287
- 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 +227 -80
- 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 +101 -27
- 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 +110 -28
- 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 +239 -86
- 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 +67 -35
- 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 +186 -54
- 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 +21 -22
- 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 +8 -3
- 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 +40 -6
- package/src/common/direction.ts +78 -51
- package/src/common/escape.ts +39 -22
- 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 +1035 -199
- package/src/common/line-format.ts +249 -0
- package/src/common/lists.ts +7 -2
- 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 +479 -90
- package/src/common/zip.ts +169 -7
- package/src/formats/csv/exporter.ts +202 -47
- 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 +890 -302
- package/src/formats/cx/index.ts +4 -3
- package/src/formats/cx2/exporter.ts +62 -339
- package/src/formats/cx2/importer.ts +318 -87
- 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 +142 -34
- 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 +113 -28
- 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 +321 -102
- 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 +90 -42
- package/src/formats/pajek/importer.ts +407 -93
- package/src/formats/pajek/index.ts +2 -1
- package/src/formats/pajek/syntax.ts +216 -55
- 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 +36 -28
- package/src/formats/xgmml/importer.ts +64 -86
- package/src/formats/xgmml/index.ts +1 -0
- package/src/formats/xgmml/values.ts +12 -4
- 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
|
@@ -50,20 +50,26 @@ import {
|
|
|
50
50
|
PRECISION_CODE,
|
|
51
51
|
} from "../../common/attributes.js";
|
|
52
52
|
import {
|
|
53
|
+
BAD_VALUE_CODE,
|
|
53
54
|
COUNT_HINT_CODE,
|
|
55
|
+
COUNT_MISMATCH_CODE,
|
|
54
56
|
DUPLICATE_ATTRIBUTE_CODE,
|
|
55
57
|
DUPLICATE_EDGE_ID_CODE,
|
|
56
58
|
DUPLICATE_NODE_CODE,
|
|
59
|
+
EMPTY_INPUT_CODE,
|
|
57
60
|
ID_MERGED_CODE,
|
|
58
61
|
MISSING_ENDPOINT_CODE,
|
|
59
62
|
MISSING_ID_CODE,
|
|
63
|
+
MULTIPLE_GRAPHS_CODE,
|
|
60
64
|
NO_GRAPH_CODE,
|
|
65
|
+
PARENT_CYCLE_CODE,
|
|
61
66
|
STRAY_TEXT_CODE,
|
|
62
67
|
UNKNOWN_ELEMENT_CODE,
|
|
63
68
|
UNKNOWN_PARENT_CODE,
|
|
69
|
+
UNKNOWN_XML_ATTRIBUTE_CODE,
|
|
64
70
|
XML_SYNTAX_CODE,
|
|
65
71
|
} from "../../common/codes.js";
|
|
66
|
-
import { type DeclaredTypeSpec, parseDecimalText } from "../../common/declared-types.js";
|
|
72
|
+
import { type DeclaredTypeSpec, overflowsToInfinity, parseDecimalText } from "../../common/declared-types.js";
|
|
67
73
|
import { DirectionResolver, type EdgeKind } from "../../common/direction.js";
|
|
68
74
|
import { IdCoercer } from "../../common/ids.js";
|
|
69
75
|
import { textChunks, throwIfAborted } from "../../common/input.js";
|
|
@@ -80,8 +86,10 @@ import { isWeightField, parseWeightText } from "../../common/weights.js";
|
|
|
80
86
|
import {
|
|
81
87
|
isWhitespace,
|
|
82
88
|
localName,
|
|
89
|
+
sniffXmlText,
|
|
83
90
|
tokenizeXml,
|
|
84
91
|
xmlDeclaredEncoding,
|
|
92
|
+
XmlEmptyInputError,
|
|
85
93
|
type XmlHandler,
|
|
86
94
|
XmlSyntaxError,
|
|
87
95
|
} from "../../common/xml.js";
|
|
@@ -102,50 +110,111 @@ import {
|
|
|
102
110
|
TIME_REPRESENTATIONS,
|
|
103
111
|
} from "./schema.js";
|
|
104
112
|
|
|
105
|
-
/**
|
|
106
|
-
|
|
113
|
+
/**
|
|
114
|
+
* The format-specific options of the GEXF importer.
|
|
115
|
+
* @category Built-in formats
|
|
116
|
+
*/
|
|
117
|
+
export interface GexfImportOptions extends CommonImportOptions {
|
|
107
118
|
/**
|
|
108
|
-
* Whether the viz namespace
|
|
109
|
-
*
|
|
119
|
+
* Whether to read the visual attributes of Gephi's viz namespace (color, position, size, shape,
|
|
120
|
+
* thickness) into node and edge attributes; false skips them, with one W_GEXF_VIZ_SKIPPED
|
|
121
|
+
* warning.
|
|
122
|
+
* @defaultValue true
|
|
110
123
|
*/
|
|
111
124
|
viz?: boolean | undefined;
|
|
112
125
|
}
|
|
113
126
|
|
|
114
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* The document is not a GEXF document (no `<gexf>` root).
|
|
129
|
+
* @category Issue and loss codes
|
|
130
|
+
*/
|
|
115
131
|
export const NOT_GEXF_CODE = "E_NOT_GEXF";
|
|
116
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* The graph declares edges but no `<nodes>` section.
|
|
134
|
+
* @category Issue and loss codes
|
|
135
|
+
*/
|
|
117
136
|
export const MISSING_NODES_CODE = "E_GEXF_MISSING_NODES";
|
|
118
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* An edge `type` outside directed / undirected / mutual.
|
|
139
|
+
* @category Issue and loss codes
|
|
140
|
+
*/
|
|
119
141
|
export const EDGE_TYPE_CODE = "E_GEXF_EDGE_TYPE";
|
|
120
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* An `<attributes>` group without a class, or with an unknown one.
|
|
144
|
+
* @category Issue and loss codes
|
|
145
|
+
*/
|
|
121
146
|
export const ATTRIBUTES_CLASS_CODE = "E_GEXF_ATTRIBUTES_CLASS";
|
|
122
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* An `<attribute>` without an id.
|
|
149
|
+
* @category Issue and loss codes
|
|
150
|
+
*/
|
|
123
151
|
export const ATTRIBUTE_ID_CODE = "E_GEXF_ATTRIBUTE_ID";
|
|
124
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* A graph header value (`defaultedgetype`, `mode`, `timeformat`, ...) outside its set.
|
|
154
|
+
* @category Issue and loss codes
|
|
155
|
+
*/
|
|
125
156
|
export const HEADER_VALUE_CODE = "W_GEXF_HEADER_VALUE";
|
|
126
|
-
/**
|
|
157
|
+
/**
|
|
158
|
+
* An `<attribute>` without a type (read as string).
|
|
159
|
+
* @category Issue and loss codes
|
|
160
|
+
*/
|
|
127
161
|
export const ATTRIBUTE_TYPE_CODE = "W_GEXF_ATTRIBUTE_TYPE";
|
|
128
|
-
/**
|
|
162
|
+
/**
|
|
163
|
+
* An `<attvalue>` naming an attribute the document never declares (once per id).
|
|
164
|
+
* @category Issue and loss codes
|
|
165
|
+
*/
|
|
129
166
|
export const UNKNOWN_ATTRIBUTE_CODE = "W_GEXF_UNKNOWN_ATTRIBUTE";
|
|
130
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* An `<attvalue>` without a `for` or a `value`.
|
|
169
|
+
* @category Issue and loss codes
|
|
170
|
+
*/
|
|
131
171
|
export const ATTVALUE_SHAPE_CODE = "W_GEXF_ATTVALUE_SHAPE";
|
|
132
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* A timed value on an attribute declared in a static group (stored as dynamic anyway).
|
|
174
|
+
* @category Issue and loss codes
|
|
175
|
+
*/
|
|
133
176
|
export const TIMED_STATIC_CODE = "W_GEXF_TIMED_VALUE_ON_STATIC";
|
|
134
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* The `weight` XML attribute is present but `weightFrom` is null.
|
|
179
|
+
* @category Issue and loss codes
|
|
180
|
+
*/
|
|
135
181
|
export const WEIGHT_IGNORED_CODE = "W_GEXF_WEIGHT_IGNORED";
|
|
136
|
-
/**
|
|
182
|
+
/**
|
|
183
|
+
* Viz elements skipped under `viz: false`.
|
|
184
|
+
* @category Issue and loss codes
|
|
185
|
+
*/
|
|
137
186
|
export const VIZ_SKIPPED_CODE = "W_GEXF_VIZ_SKIPPED";
|
|
138
|
-
/**
|
|
187
|
+
/**
|
|
188
|
+
* A 1.2 dynamic viz element (its bounds are dropped, the value kept).
|
|
189
|
+
* @category Issue and loss codes
|
|
190
|
+
*/
|
|
139
191
|
export const VIZ_DYNAMIC_CODE = "W_GEXF_VIZ_DYNAMIC_DROPPED";
|
|
140
192
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
193
|
+
* `startopen` / `endopen` on a `<spell>` stored closed. No longer recorded: the open bits of each spell are kept in
|
|
194
|
+
* the spells.open column. Kept so the exported code table is stable.
|
|
195
|
+
* @category Issue and loss codes
|
|
143
196
|
*/
|
|
144
197
|
export const SPELL_OPEN_CODE = "W_GEXF_SPELL_OPEN_DROPPED";
|
|
145
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* A viz element with a value that does not parse (the element is skipped).
|
|
200
|
+
* @category Issue and loss codes
|
|
201
|
+
*/
|
|
146
202
|
export const VIZ_VALUE_CODE = "W_GEXF_VIZ_VALUE";
|
|
147
|
-
/**
|
|
203
|
+
/**
|
|
204
|
+
* Both `start` and `startopen` (or `end` and `endopen`) on one element; the closed bound wins.
|
|
205
|
+
* @category Issue and loss codes
|
|
206
|
+
*/
|
|
148
207
|
export const OPEN_BOUND_CONFLICT_CODE = "W_GEXF_OPEN_BOUND_CONFLICT";
|
|
208
|
+
/**
|
|
209
|
+
* Both `timestamp` and `start` / `end` on one element or value; `start` / `end` win.
|
|
210
|
+
* @category Issue and loss codes
|
|
211
|
+
*/
|
|
212
|
+
export const TIMESTAMP_CONFLICT_CODE = "W_GEXF_TIMESTAMP_CONFLICT";
|
|
213
|
+
/**
|
|
214
|
+
* A value outside the `<options>` its attribute declares (the value is kept).
|
|
215
|
+
* @category Issue and loss codes
|
|
216
|
+
*/
|
|
217
|
+
export const VALUE_OUTSIDE_OPTIONS_CODE = "W_GEXF_VALUE_OUTSIDE_OPTIONS";
|
|
149
218
|
|
|
150
219
|
export {
|
|
151
220
|
DUPLICATE_NODE_CODE,
|
|
@@ -156,6 +225,85 @@ export {
|
|
|
156
225
|
UNKNOWN_PARENT_CODE,
|
|
157
226
|
};
|
|
158
227
|
|
|
228
|
+
/** The GEXF versions graph-io knows, and the version a namespace implies when `version` is missing. */
|
|
229
|
+
const VERSIONS: ReadonlySet<string> = new Set(["1.0", "1.1", "1.2", "1.3"]);
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* The version a GEXF namespace names (`http://www.gexf.net/1.2draft`, `http://gexf.net/1.3`).
|
|
233
|
+
* @param ns - the root's namespace
|
|
234
|
+
* @returns the version, or null
|
|
235
|
+
*/
|
|
236
|
+
function namespaceVersion(ns: string | null): string | null {
|
|
237
|
+
const match = ns === null ? null : /gexf\.net\/(1\.[0-3])/.exec(ns);
|
|
238
|
+
return match === null ? null : match[1];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Whether a namespace is a GEXF viz namespace (`http://www.gexf.net/1.2draft/viz`, the 1.1
|
|
243
|
+
* triple-slash `http://gexf.net/1.1draft///viz`, `http://gexf.net/1.3/viz`, GEXF 1.0's `http://www.gephi.org/gexf/viz`).
|
|
244
|
+
* @param ns - the element's namespace
|
|
245
|
+
* @returns true for a viz namespace
|
|
246
|
+
*/
|
|
247
|
+
function isVizNamespace(ns: string | null): boolean {
|
|
248
|
+
return ns !== null && /(gexf\.net\/.*viz|gephi\.org\/gexf\/viz)\/?$/.test(ns);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Distinct unknown attribute names reported one by one per element kind; the rest are counted in one issue. */
|
|
252
|
+
const MAX_UNKNOWN_ATTRIBUTE_NAMES = 16;
|
|
253
|
+
|
|
254
|
+
/** The XML attributes the importer reads on each element other than `<node>` / `<edge>`. */
|
|
255
|
+
const ELEMENT_ATTRIBUTES: Readonly<Record<string, ReadonlySet<string>>> = {
|
|
256
|
+
gexf: new Set(["version", "variant"]),
|
|
257
|
+
meta: new Set(["lastmodifieddate"]),
|
|
258
|
+
graph: new Set([
|
|
259
|
+
"defaultedgetype",
|
|
260
|
+
"mode",
|
|
261
|
+
"type",
|
|
262
|
+
"timeformat",
|
|
263
|
+
"timerepresentation",
|
|
264
|
+
"idtype",
|
|
265
|
+
"start",
|
|
266
|
+
"end",
|
|
267
|
+
"timestamp",
|
|
268
|
+
"startopen",
|
|
269
|
+
"endopen",
|
|
270
|
+
]),
|
|
271
|
+
// `type` is GEXF 1.0's name for `mode` (static | dynamic)
|
|
272
|
+
attributes: new Set(["class", "mode", "type", "start", "end", "startopen", "endopen"]),
|
|
273
|
+
attribute: new Set(["id", "title", "type"]),
|
|
274
|
+
attvalue: new Set(["for", "value", "start", "end", "timestamp", "startopen", "endopen", "id"]),
|
|
275
|
+
spell: new Set(["start", "end", "timestamp", "startopen", "endopen"]),
|
|
276
|
+
parent: new Set(["for"]),
|
|
277
|
+
nodes: new Set(["count"]),
|
|
278
|
+
edges: new Set(["count"]),
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/** A `count` hint as written: decimal digits only (as GraphML's parse.nodes). */
|
|
282
|
+
const COUNT_TEXT = /^\d+$/;
|
|
283
|
+
|
|
284
|
+
/** An xs:date / xs:dateTime text (`lastmodifieddate`). */
|
|
285
|
+
const DATE_TEXT = /^-?\d{4,}-\d{2}-\d{2}(.*)$/s;
|
|
286
|
+
|
|
287
|
+
/** The time of an xs:dateTime, after its date. */
|
|
288
|
+
const TIME_TEXT = /^T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?/;
|
|
289
|
+
|
|
290
|
+
/** The zone that may end an xs:date or xs:dateTime. */
|
|
291
|
+
const ZONE_TEXT = /^(?:Z|[+-]\d{2}:\d{2})?$/;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Whether a text is an xs:date or xs:dateTime.
|
|
295
|
+
* @param text - the text
|
|
296
|
+
* @returns true when it is
|
|
297
|
+
*/
|
|
298
|
+
function isDateText(text: string): boolean {
|
|
299
|
+
const m = DATE_TEXT.exec(text);
|
|
300
|
+
if (m === null) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
const time = TIME_TEXT.exec(m[1])?.[0] ?? "";
|
|
304
|
+
return ZONE_TEXT.test(m[1].slice(time.length));
|
|
305
|
+
}
|
|
306
|
+
|
|
159
307
|
const EDGE_TYPES: ReadonlySet<string> = new Set(["directed", "undirected", "mutual"]);
|
|
160
308
|
|
|
161
309
|
/**
|
|
@@ -407,6 +555,8 @@ interface EdgeFrame {
|
|
|
407
555
|
index: number;
|
|
408
556
|
mirror: number;
|
|
409
557
|
readonly where: MutableLocation;
|
|
558
|
+
/** The `weight` XML attribute as written, or null, for the clash with a weight attvalue. */
|
|
559
|
+
xmlWeight: string | null;
|
|
410
560
|
}
|
|
411
561
|
|
|
412
562
|
/** A location whose fields are overwritten per element (see EdgeFrame). */
|
|
@@ -502,8 +652,25 @@ class GexfReader implements XmlHandler {
|
|
|
502
652
|
index: INVALID_INDEX,
|
|
503
653
|
mirror: INVALID_INDEX,
|
|
504
654
|
where: { line: 0, element: null },
|
|
655
|
+
xmlWeight: null,
|
|
505
656
|
};
|
|
506
657
|
|
|
658
|
+
/** The `for` ids of the static values of the open `<attvalues>`, for the duplicate check. */
|
|
659
|
+
private readonly attvalueIds = new Set<string>();
|
|
660
|
+
|
|
661
|
+
/** The open `<nodes>` / `<edges>` sections: their count hint and the elements read directly in them. */
|
|
662
|
+
private readonly sections: {
|
|
663
|
+
readonly domain: "node" | "edge";
|
|
664
|
+
readonly hint: number | null;
|
|
665
|
+
seen: number;
|
|
666
|
+
readonly line: number;
|
|
667
|
+
}[] = [];
|
|
668
|
+
|
|
669
|
+
/** Unknown attribute names reported per element kind, and how many more were not listed. */
|
|
670
|
+
private readonly unknownNames = new Map<string, Set<string>>();
|
|
671
|
+
|
|
672
|
+
private readonly unknownOverflow = new Map<string, number>();
|
|
673
|
+
|
|
507
674
|
/** The one `<attvalue>` location, reused per value (its fields are copied into every issue). */
|
|
508
675
|
private readonly valueWhere: MutableLocation = { line: 0, element: null };
|
|
509
676
|
|
|
@@ -572,15 +739,28 @@ class GexfReader implements XmlHandler {
|
|
|
572
739
|
|
|
573
740
|
// ---------------------------------------------------------------- events
|
|
574
741
|
|
|
575
|
-
start(name: string, attrs: ReadonlyMap<string, string>, line: number): void {
|
|
742
|
+
start(name: string, attrs: ReadonlyMap<string, string>, line: number, ns: string | null = ""): void {
|
|
576
743
|
const parent = this.ctx.length === 0 ? null : this.ctx[this.ctx.length - 1];
|
|
577
744
|
const local = localName(name);
|
|
745
|
+
if (
|
|
746
|
+
ns === null &&
|
|
747
|
+
parent !== null &&
|
|
748
|
+
parent !== Ctx.Skip &&
|
|
749
|
+
parent !== Ctx.MetaText &&
|
|
750
|
+
parent !== Ctx.AttributeText &&
|
|
751
|
+
!name.startsWith("viz:")
|
|
752
|
+
) {
|
|
753
|
+
// an undeclared prefix or a name that is not namespace-well-formed (<:node>, <a:b:node>)
|
|
754
|
+
this.unknownElement(name, line);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
578
757
|
switch (parent) {
|
|
579
758
|
case null:
|
|
580
759
|
if (local !== "gexf") {
|
|
581
760
|
this.report.fail(NOT_GEXF_CODE, `the root element is <${name}>, not <gexf>`, { line });
|
|
582
761
|
}
|
|
583
|
-
this.
|
|
762
|
+
this.checkAttributes("gexf", attrs, line);
|
|
763
|
+
this.readRoot(attrs, ns, line);
|
|
584
764
|
this.ctx.push(Ctx.Gexf);
|
|
585
765
|
return;
|
|
586
766
|
case Ctx.Gexf:
|
|
@@ -602,7 +782,7 @@ class GexfReader implements XmlHandler {
|
|
|
602
782
|
this.startInNodes(name, local, attrs, line);
|
|
603
783
|
return;
|
|
604
784
|
case Ctx.Node:
|
|
605
|
-
this.startInNode(name, local, attrs, line);
|
|
785
|
+
this.startInNode(name, local, attrs, line, ns);
|
|
606
786
|
return;
|
|
607
787
|
case Ctx.Attvalues:
|
|
608
788
|
this.startInAttvalues(name, local, attrs, line);
|
|
@@ -617,7 +797,7 @@ class GexfReader implements XmlHandler {
|
|
|
617
797
|
this.startInEdges(name, local, attrs, line);
|
|
618
798
|
return;
|
|
619
799
|
case Ctx.Edge:
|
|
620
|
-
this.startInEdge(name, local, attrs, line);
|
|
800
|
+
this.startInEdge(name, local, attrs, line, ns);
|
|
621
801
|
return;
|
|
622
802
|
case Ctx.MetaText:
|
|
623
803
|
case Ctx.AttributeText:
|
|
@@ -658,6 +838,10 @@ class GexfReader implements XmlHandler {
|
|
|
658
838
|
case Ctx.Node:
|
|
659
839
|
this.nodeStack.pop();
|
|
660
840
|
return;
|
|
841
|
+
case Ctx.Nodes:
|
|
842
|
+
case Ctx.Edges:
|
|
843
|
+
this.finishSection();
|
|
844
|
+
return;
|
|
661
845
|
case Ctx.Edge:
|
|
662
846
|
this.edge = null;
|
|
663
847
|
return;
|
|
@@ -668,8 +852,6 @@ class GexfReader implements XmlHandler {
|
|
|
668
852
|
this.finishSpells();
|
|
669
853
|
return;
|
|
670
854
|
case Ctx.Gexf:
|
|
671
|
-
case Ctx.Nodes:
|
|
672
|
-
case Ctx.Edges:
|
|
673
855
|
case Ctx.Attvalues:
|
|
674
856
|
case Ctx.Skip:
|
|
675
857
|
case undefined:
|
|
@@ -717,18 +899,42 @@ class GexfReader implements XmlHandler {
|
|
|
717
899
|
if (!this.graphSeen) {
|
|
718
900
|
this.report.fail(NO_GRAPH_CODE, "the document has no <graph> element");
|
|
719
901
|
}
|
|
902
|
+
for (const [kind, more] of this.unknownOverflow) {
|
|
903
|
+
this.report.warning(
|
|
904
|
+
"unsupported",
|
|
905
|
+
UNKNOWN_XML_ATTRIBUTE_CODE,
|
|
906
|
+
`${more} more distinct attribute names on <${kind}> are not GEXF attributes and are not kept (not listed one by one)`,
|
|
907
|
+
{ element: kind },
|
|
908
|
+
);
|
|
909
|
+
}
|
|
720
910
|
}
|
|
721
911
|
|
|
722
912
|
// ---------------------------------------------------------------- root, meta, graph
|
|
723
913
|
|
|
724
914
|
/**
|
|
725
|
-
* Record the root's version
|
|
915
|
+
* Record the root's version: as written when known; the namespace's when absent; an unknown
|
|
916
|
+
* one is kept as written and reported.
|
|
726
917
|
* @param attrs - the `<gexf>` attributes
|
|
918
|
+
* @param ns - the root's namespace
|
|
919
|
+
* @param line - the line
|
|
727
920
|
*/
|
|
728
|
-
private readRoot(attrs: ReadonlyMap<string, string
|
|
921
|
+
private readRoot(attrs: ReadonlyMap<string, string>, ns: string | null, line: number): void {
|
|
729
922
|
const version = attrs.get("version");
|
|
730
923
|
if (version !== undefined && version.length > 0) {
|
|
731
924
|
this.meta.sourceVersion = version;
|
|
925
|
+
if (!VERSIONS.has(version)) {
|
|
926
|
+
this.report.warning(
|
|
927
|
+
"validation-error",
|
|
928
|
+
HEADER_VALUE_CODE,
|
|
929
|
+
`<gexf version="${version}"> is not a GEXF version graph-io knows (1.0 to 1.3); read as GEXF 1.3`,
|
|
930
|
+
{ line, element: "version" },
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const implied = namespaceVersion(ns);
|
|
936
|
+
if (implied !== null) {
|
|
937
|
+
this.meta.sourceVersion = implied;
|
|
732
938
|
}
|
|
733
939
|
}
|
|
734
940
|
|
|
@@ -742,21 +948,36 @@ class GexfReader implements XmlHandler {
|
|
|
742
948
|
private startInGexf(name: string, local: string, attrs: ReadonlyMap<string, string>, line: number): void {
|
|
743
949
|
switch (local) {
|
|
744
950
|
case "meta": {
|
|
951
|
+
this.checkAttributes("meta", attrs, line);
|
|
745
952
|
const modified = attrs.get("lastmodifieddate");
|
|
746
953
|
if (modified !== undefined && modified.length > 0) {
|
|
747
954
|
this.meta.modified = modified;
|
|
955
|
+
if (!isDateText(modified.trim())) {
|
|
956
|
+
this.report.warning(
|
|
957
|
+
"validation-error",
|
|
958
|
+
HEADER_VALUE_CODE,
|
|
959
|
+
`<meta lastmodifieddate="${modified}"> is not an xs:date; kept as written`,
|
|
960
|
+
{ line, element: "lastmodifieddate" },
|
|
961
|
+
);
|
|
962
|
+
}
|
|
748
963
|
}
|
|
749
964
|
this.ctx.push(Ctx.Meta);
|
|
750
965
|
return;
|
|
751
966
|
}
|
|
752
967
|
case "graph":
|
|
968
|
+
this.checkAttributes("graph", attrs, line);
|
|
753
969
|
if (this.graphSeen) {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
970
|
+
// the schema allows one graph; merging is what the GraphML importer does too
|
|
971
|
+
this.report.warnOnce(
|
|
972
|
+
"unsupported",
|
|
973
|
+
MULTIPLE_GRAPHS_CODE,
|
|
974
|
+
"a second <graph> element; its nodes and edges are merged into the first and its header is ignored",
|
|
975
|
+
{ line },
|
|
976
|
+
);
|
|
977
|
+
} else {
|
|
978
|
+
this.graphSeen = true;
|
|
979
|
+
this.readHeader(attrs, line);
|
|
757
980
|
}
|
|
758
|
-
this.graphSeen = true;
|
|
759
|
-
this.readHeader(attrs, line);
|
|
760
981
|
this.ctx.push(Ctx.Graph);
|
|
761
982
|
return;
|
|
762
983
|
default:
|
|
@@ -803,9 +1024,11 @@ class GexfReader implements XmlHandler {
|
|
|
803
1024
|
this.metaText = "";
|
|
804
1025
|
}
|
|
805
1026
|
|
|
806
|
-
/** Close `<meta>`:
|
|
1027
|
+
/** Close `<meta>`: the patch is applied with the graph header, or now when `<meta>` follows the graph. */
|
|
807
1028
|
private finishMeta(): void {
|
|
808
|
-
|
|
1029
|
+
if (this.graphSeen) {
|
|
1030
|
+
this.sink.setMeta(this.meta);
|
|
1031
|
+
}
|
|
809
1032
|
}
|
|
810
1033
|
|
|
811
1034
|
/**
|
|
@@ -855,7 +1078,7 @@ class GexfReader implements XmlHandler {
|
|
|
855
1078
|
const idType = attrs.get("idtype");
|
|
856
1079
|
if (idType !== undefined) {
|
|
857
1080
|
if (idType === "string") {
|
|
858
|
-
// informational only: Gephi writes idtype="string" unconditionally, so
|
|
1081
|
+
// informational only: Gephi writes idtype="string" unconditionally, so honoring it
|
|
859
1082
|
// would turn every Gephi export's integer ids into text; the canonical rule of
|
|
860
1083
|
// design section 4.1 applies and the exporter's check() reports W_ID_TEXT_TYPE
|
|
861
1084
|
patch.idType = "string";
|
|
@@ -908,8 +1131,7 @@ class GexfReader implements XmlHandler {
|
|
|
908
1131
|
return;
|
|
909
1132
|
case "nodes":
|
|
910
1133
|
this.nodesSeen = true;
|
|
911
|
-
this.
|
|
912
|
-
this.ctx.push(Ctx.Nodes);
|
|
1134
|
+
this.beginSection(attrs, "node", line);
|
|
913
1135
|
return;
|
|
914
1136
|
case "edges":
|
|
915
1137
|
if (!this.nodesSeen) {
|
|
@@ -921,37 +1143,65 @@ class GexfReader implements XmlHandler {
|
|
|
921
1143
|
);
|
|
922
1144
|
}
|
|
923
1145
|
this.resolveParents();
|
|
924
|
-
this.
|
|
925
|
-
this.ctx.push(Ctx.Edges);
|
|
1146
|
+
this.beginSection(attrs, "edge", line);
|
|
926
1147
|
return;
|
|
927
1148
|
default:
|
|
928
1149
|
this.unknownElement(name, line);
|
|
929
1150
|
}
|
|
930
1151
|
}
|
|
931
1152
|
|
|
1153
|
+
/**
|
|
1154
|
+
* Open a `<nodes>` / `<edges>` section: its count hint is reserved and later compared with the
|
|
1155
|
+
* elements the section holds.
|
|
1156
|
+
* @param attrs - the section's attributes
|
|
1157
|
+
* @param domain - node or edge
|
|
1158
|
+
* @param line - the line
|
|
1159
|
+
*/
|
|
1160
|
+
private beginSection(attrs: ReadonlyMap<string, string>, domain: "node" | "edge", line: number): void {
|
|
1161
|
+
this.checkAttributes(`${domain}s`, attrs, line);
|
|
1162
|
+
this.sections.push({ domain, hint: this.reserve(attrs, domain, line), seen: 0, line });
|
|
1163
|
+
this.ctx.push(domain === "node" ? Ctx.Nodes : Ctx.Edges);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/** Close a `<nodes>` / `<edges>` section: a count hint that disagrees is reported. */
|
|
1167
|
+
private finishSection(): void {
|
|
1168
|
+
const section = this.sections.pop();
|
|
1169
|
+
if (section?.hint === undefined || section.hint === null || section.hint === section.seen) {
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
this.report.warning(
|
|
1173
|
+
"validation-error",
|
|
1174
|
+
COUNT_MISMATCH_CODE,
|
|
1175
|
+
`<${section.domain}s count="${section.hint}"> but the section holds ${section.seen} <${section.domain}> elements (nested ones included)`,
|
|
1176
|
+
{ line: section.line, element: "count" },
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
932
1180
|
/**
|
|
933
1181
|
* Pass a section's `count` hint to the sink; a hint the sink cannot reserve is a warning.
|
|
934
1182
|
* @param attrs - the `<nodes>` or `<edges>` attributes
|
|
935
1183
|
* @param domain - which count it is
|
|
936
1184
|
* @param line - the line
|
|
1185
|
+
* @returns the hint, or null when absent or ignored
|
|
937
1186
|
*/
|
|
938
|
-
private reserve(attrs: ReadonlyMap<string, string>, domain: "node" | "edge", line: number):
|
|
1187
|
+
private reserve(attrs: ReadonlyMap<string, string>, domain: "node" | "edge", line: number): number | null {
|
|
939
1188
|
const text = attrs.get("count");
|
|
940
1189
|
if (text === undefined) {
|
|
941
|
-
return;
|
|
1190
|
+
return null;
|
|
942
1191
|
}
|
|
943
|
-
|
|
944
|
-
|
|
1192
|
+
// decimal digits only: Number() would read "" as 0, "0x10" as 16 and "1e3" as 1000
|
|
1193
|
+
const count = COUNT_TEXT.test(text) ? Number(text) : Number.NaN;
|
|
1194
|
+
if (!Number.isSafeInteger(count) || count > MAX_COUNT) {
|
|
945
1195
|
this.report.warning(
|
|
946
1196
|
"validation-error",
|
|
947
1197
|
COUNT_HINT_CODE,
|
|
948
1198
|
`<${domain}s count="${text}"> is not a count the sink can reserve; ignored`,
|
|
949
1199
|
{ line, element: "count" },
|
|
950
1200
|
);
|
|
951
|
-
return;
|
|
1201
|
+
return null;
|
|
952
1202
|
}
|
|
953
1203
|
if (count === 0) {
|
|
954
|
-
return;
|
|
1204
|
+
return count;
|
|
955
1205
|
}
|
|
956
1206
|
try {
|
|
957
1207
|
if (domain === "node") {
|
|
@@ -968,6 +1218,7 @@ class GexfReader implements XmlHandler {
|
|
|
968
1218
|
element: "count",
|
|
969
1219
|
});
|
|
970
1220
|
}
|
|
1221
|
+
return count;
|
|
971
1222
|
}
|
|
972
1223
|
|
|
973
1224
|
// ---------------------------------------------------------------- attribute declarations
|
|
@@ -978,6 +1229,7 @@ class GexfReader implements XmlHandler {
|
|
|
978
1229
|
* @param line - the line
|
|
979
1230
|
*/
|
|
980
1231
|
private beginAttributes(attrs: ReadonlyMap<string, string>, line: number): void {
|
|
1232
|
+
this.checkAttributes("attributes", attrs, line);
|
|
981
1233
|
const cls = attrs.get("class");
|
|
982
1234
|
const where = { line };
|
|
983
1235
|
let domain: "node" | "edge" | null = null;
|
|
@@ -993,7 +1245,7 @@ class GexfReader implements XmlHandler {
|
|
|
993
1245
|
where,
|
|
994
1246
|
);
|
|
995
1247
|
}
|
|
996
|
-
const mode = attrs.get("mode");
|
|
1248
|
+
const mode = attrs.get("mode") ?? attrs.get("type");
|
|
997
1249
|
if (mode !== undefined && mode !== "static" && mode !== "dynamic") {
|
|
998
1250
|
this.warnHeader("mode", mode, where);
|
|
999
1251
|
}
|
|
@@ -1013,6 +1265,7 @@ class GexfReader implements XmlHandler {
|
|
|
1013
1265
|
this.unknownElement(name, line);
|
|
1014
1266
|
return;
|
|
1015
1267
|
}
|
|
1268
|
+
this.checkAttributes("attribute", attrs, line);
|
|
1016
1269
|
const group = this.attributeGroup;
|
|
1017
1270
|
if (group === null || group.domain === null) {
|
|
1018
1271
|
// the group's class was refused; its attributes are dropped with it
|
|
@@ -1099,6 +1352,14 @@ class GexfReader implements XmlHandler {
|
|
|
1099
1352
|
);
|
|
1100
1353
|
return;
|
|
1101
1354
|
}
|
|
1355
|
+
if (this.unknownAttributes.has(`${domain}:${id}`)) {
|
|
1356
|
+
report.warning(
|
|
1357
|
+
"missing-value",
|
|
1358
|
+
UNKNOWN_ATTRIBUTE_CODE,
|
|
1359
|
+
`${domain} attribute "${id}" is declared after values that use it; those values were dropped (declare <attributes> before <nodes>)`,
|
|
1360
|
+
where,
|
|
1361
|
+
);
|
|
1362
|
+
}
|
|
1102
1363
|
const title = attrs.get("title") ?? null;
|
|
1103
1364
|
const type = attrs.get("type") ?? null;
|
|
1104
1365
|
if (type === null) {
|
|
@@ -1110,7 +1371,15 @@ class GexfReader implements XmlHandler {
|
|
|
1110
1371
|
);
|
|
1111
1372
|
}
|
|
1112
1373
|
const name = title !== null && title.length > 0 ? title : id;
|
|
1113
|
-
if (domain === "edge" && isWeightField(name, options.weightFrom)) {
|
|
1374
|
+
if (domain === "edge" && this.weightAttr !== null && isWeightField(name, options.weightFrom)) {
|
|
1375
|
+
// the first attribute of that title stays THE weight; this one becomes a plain column
|
|
1376
|
+
report.warning(
|
|
1377
|
+
"validation-error",
|
|
1378
|
+
DUPLICATE_ATTRIBUTE_CODE,
|
|
1379
|
+
`edge attribute "${id}" is also titled ${name}; attribute "${this.weightAttr.id}" is the weight, "${id}" is kept as a column`,
|
|
1380
|
+
where,
|
|
1381
|
+
);
|
|
1382
|
+
} else if (domain === "edge" && isWeightField(name, options.weightFrom)) {
|
|
1114
1383
|
const declared = declareAttribute({ format: GEXF_FORMAT, id, title, type, long: options.long });
|
|
1115
1384
|
for (const issue of declared.issues) {
|
|
1116
1385
|
report.warning(issue.category, issue.code, issue.message, where);
|
|
@@ -1121,7 +1390,9 @@ class GexfReader implements XmlHandler {
|
|
|
1121
1390
|
}
|
|
1122
1391
|
const declaredNames = domain === "node" ? this.declaredNodeNames : this.declaredEdgeNames;
|
|
1123
1392
|
const reserved = domain === "node" ? RESERVED_NODE_NAMES : RESERVED_EDGE_NAMES;
|
|
1124
|
-
const
|
|
1393
|
+
const weightName = domain === "edge" && this.weightAttr !== null ? options.weightFrom : null;
|
|
1394
|
+
const taken = (candidate: string): boolean =>
|
|
1395
|
+
reserved.has(candidate) || declaredNames.has(candidate) || candidate === weightName;
|
|
1125
1396
|
const input: AttributeDeclarationInput = {
|
|
1126
1397
|
format: GEXF_FORMAT,
|
|
1127
1398
|
id,
|
|
@@ -1197,7 +1468,9 @@ class GexfReader implements XmlHandler {
|
|
|
1197
1468
|
const enclosing = this.nodeStack.length === 0 ? INVALID_INDEX : this.nodeStack[this.nodeStack.length - 1].index;
|
|
1198
1469
|
const idText = attrs.get("id");
|
|
1199
1470
|
const where: IssueLocation = { line, element: idText ?? attrs.get("label") ?? null };
|
|
1471
|
+
this.countInSection("node");
|
|
1200
1472
|
this.checkAttributeNames(attrs, NODE_ATTRIBUTES, "node", where);
|
|
1473
|
+
// GEXF types ids as xs:string: id="" is a legal id (the exporter writes it), only a missing one is not
|
|
1201
1474
|
if (idText === undefined) {
|
|
1202
1475
|
report.error("missing-value", MISSING_ID_CODE, "<node> without an id", where);
|
|
1203
1476
|
report.counts.skippedNodes++;
|
|
@@ -1205,6 +1478,15 @@ class GexfReader implements XmlHandler {
|
|
|
1205
1478
|
this.ctx.push(Ctx.Node);
|
|
1206
1479
|
return;
|
|
1207
1480
|
}
|
|
1481
|
+
if (this.meta.idType === "integer" && !/^\s*[+-]?\d+\s*$/.test(idText)) {
|
|
1482
|
+
report.warnOnce(
|
|
1483
|
+
"validation-error",
|
|
1484
|
+
HEADER_VALUE_CODE,
|
|
1485
|
+
`node id "${idText}" is not an integer although the graph declares idtype="integer"; ids follow the ids option`,
|
|
1486
|
+
where,
|
|
1487
|
+
`${HEADER_VALUE_CODE}:idtype`,
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1208
1490
|
let index: number;
|
|
1209
1491
|
try {
|
|
1210
1492
|
const id = this.coerceId(idText, where);
|
|
@@ -1230,11 +1512,16 @@ class GexfReader implements XmlHandler {
|
|
|
1230
1512
|
}
|
|
1231
1513
|
this.nodeStack.push({ index, where });
|
|
1232
1514
|
this.ctx.push(Ctx.Node);
|
|
1515
|
+
// three independent parts: a bad pid must not cost the label or the lifetime
|
|
1233
1516
|
try {
|
|
1234
1517
|
const label = attrs.get("label");
|
|
1235
1518
|
if (label !== undefined) {
|
|
1236
1519
|
this.nodeColumns.label.set(sink, report, index, label, where);
|
|
1237
1520
|
}
|
|
1521
|
+
} catch (err) {
|
|
1522
|
+
report.recordError(err, where);
|
|
1523
|
+
}
|
|
1524
|
+
try {
|
|
1238
1525
|
const pid = attrs.get("pid");
|
|
1239
1526
|
if (pid !== undefined) {
|
|
1240
1527
|
this.parentRefs.push({
|
|
@@ -1246,28 +1533,61 @@ class GexfReader implements XmlHandler {
|
|
|
1246
1533
|
} else if (enclosing !== INVALID_INDEX) {
|
|
1247
1534
|
this.parentRefs.push({ child: index, parentId: null, parentIndex: enclosing, where });
|
|
1248
1535
|
}
|
|
1536
|
+
} catch (err) {
|
|
1537
|
+
report.recordError(err, where);
|
|
1538
|
+
}
|
|
1539
|
+
try {
|
|
1249
1540
|
this.writeLifetime(attrs, index, this.nodeColumns, where);
|
|
1250
1541
|
} catch (err) {
|
|
1251
1542
|
report.recordError(err, where);
|
|
1252
1543
|
}
|
|
1253
1544
|
}
|
|
1254
1545
|
|
|
1546
|
+
/**
|
|
1547
|
+
* Count a `<node>` / `<edge>` element in the open sections of its kind, for the count hint
|
|
1548
|
+
* check: a `<nodes count>` counts the nodes nested in its nodes too.
|
|
1549
|
+
* @param domain - node or edge
|
|
1550
|
+
*/
|
|
1551
|
+
private countInSection(domain: "node" | "edge"): void {
|
|
1552
|
+
for (const section of this.sections) {
|
|
1553
|
+
if (section.domain === domain) {
|
|
1554
|
+
section.seen++;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1255
1559
|
/**
|
|
1256
1560
|
* A child of `<node>`.
|
|
1257
1561
|
* @param name - the element name
|
|
1258
1562
|
* @param local - its local name
|
|
1259
1563
|
* @param attrs - its attributes
|
|
1260
1564
|
* @param line - the line
|
|
1565
|
+
* @param ns - the element's namespace
|
|
1261
1566
|
*/
|
|
1262
|
-
private startInNode(
|
|
1567
|
+
private startInNode(
|
|
1568
|
+
name: string,
|
|
1569
|
+
local: string,
|
|
1570
|
+
attrs: ReadonlyMap<string, string>,
|
|
1571
|
+
line: number,
|
|
1572
|
+
ns: string | null,
|
|
1573
|
+
): void {
|
|
1263
1574
|
const frame = this.nodeStack[this.nodeStack.length - 1];
|
|
1264
1575
|
if (frame.index === INVALID_INDEX && local !== "nodes") {
|
|
1265
1576
|
// the node was skipped; its children are too
|
|
1266
1577
|
this.ctx.push(Ctx.Skip);
|
|
1267
1578
|
return;
|
|
1268
1579
|
}
|
|
1580
|
+
if (frame.index === INVALID_INDEX) {
|
|
1581
|
+
this.report.error(
|
|
1582
|
+
"missing-value",
|
|
1583
|
+
UNKNOWN_PARENT_CODE,
|
|
1584
|
+
"the nodes nested in this skipped <node> lose their parent; they are read as top-level nodes",
|
|
1585
|
+
{ line },
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1269
1588
|
switch (local) {
|
|
1270
1589
|
case "attvalues":
|
|
1590
|
+
this.attvalueIds.clear();
|
|
1271
1591
|
this.ctx.push(Ctx.Attvalues);
|
|
1272
1592
|
return;
|
|
1273
1593
|
case "parents":
|
|
@@ -1281,13 +1601,16 @@ class GexfReader implements XmlHandler {
|
|
|
1281
1601
|
this.ctx.push(Ctx.Spells);
|
|
1282
1602
|
return;
|
|
1283
1603
|
case "nodes":
|
|
1284
|
-
this.
|
|
1285
|
-
this.ctx.push(Ctx.Nodes);
|
|
1604
|
+
this.beginSection(attrs, "node", line);
|
|
1286
1605
|
return;
|
|
1287
1606
|
case "color":
|
|
1288
1607
|
case "position":
|
|
1289
1608
|
case "size":
|
|
1290
1609
|
case "shape":
|
|
1610
|
+
if (!isViz(name, ns)) {
|
|
1611
|
+
this.unknownElement(name, line);
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1291
1614
|
this.writeViz(name, local, attrs, "node", frame.index, frame.where);
|
|
1292
1615
|
this.ctx.push(Ctx.Skip);
|
|
1293
1616
|
return;
|
|
@@ -1309,10 +1632,16 @@ class GexfReader implements XmlHandler {
|
|
|
1309
1632
|
return;
|
|
1310
1633
|
}
|
|
1311
1634
|
this.ctx.push(Ctx.Skip);
|
|
1635
|
+
this.checkAttributes("parent", attrs, line);
|
|
1312
1636
|
const frame = this.nodeStack[this.nodeStack.length - 1];
|
|
1313
1637
|
const text = attrs.get("for");
|
|
1314
1638
|
if (text === undefined) {
|
|
1315
|
-
this.report.
|
|
1639
|
+
this.report.error(
|
|
1640
|
+
"missing-value",
|
|
1641
|
+
UNKNOWN_PARENT_CODE,
|
|
1642
|
+
"<parent> without a for attribute names no parent",
|
|
1643
|
+
frame.where,
|
|
1644
|
+
);
|
|
1316
1645
|
return;
|
|
1317
1646
|
}
|
|
1318
1647
|
try {
|
|
@@ -1341,6 +1670,20 @@ class GexfReader implements XmlHandler {
|
|
|
1341
1670
|
const lists = this.parentsRefs;
|
|
1342
1671
|
this.parentRefs = [];
|
|
1343
1672
|
this.parentsRefs = [];
|
|
1673
|
+
// each node's parent so far, as the parent column holds it: a link closes a cycle when the
|
|
1674
|
+
// child is the parent or one of its ancestors. A repeated link (a duplicate node declaring
|
|
1675
|
+
// the same pid) is not a cycle, which an undirected union-find would wrongly report.
|
|
1676
|
+
// ponytail: O(depth) per link, so O(n * depth); fine for real hierarchies, add path
|
|
1677
|
+
// compression over an ancestry index if a deep chain ever shows up in a profile.
|
|
1678
|
+
const parentOf = new Map<number, number>();
|
|
1679
|
+
const isAncestorOrSelf = (candidate: number, of: number): boolean => {
|
|
1680
|
+
for (let at: number | undefined = of; at !== undefined; at = parentOf.get(at)) {
|
|
1681
|
+
if (at === candidate) {
|
|
1682
|
+
return true;
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
return false;
|
|
1686
|
+
};
|
|
1344
1687
|
for (const ref of refs) {
|
|
1345
1688
|
let parent = ref.parentIndex;
|
|
1346
1689
|
if (ref.parentId !== null) {
|
|
@@ -1355,8 +1698,21 @@ class GexfReader implements XmlHandler {
|
|
|
1355
1698
|
continue;
|
|
1356
1699
|
}
|
|
1357
1700
|
}
|
|
1701
|
+
if (parentOf.get(ref.child) === parent) {
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
if (isAncestorOrSelf(ref.child, parent)) {
|
|
1705
|
+
report.error(
|
|
1706
|
+
"validation-error",
|
|
1707
|
+
PARENT_CYCLE_CODE,
|
|
1708
|
+
`the parent link of node "${ref.where.element ?? "?"}" would close a parent cycle; it is dropped`,
|
|
1709
|
+
ref.where,
|
|
1710
|
+
);
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
1358
1713
|
try {
|
|
1359
1714
|
this.parentColumn.set(sink, report, ref.child, parent, ref.where);
|
|
1715
|
+
parentOf.set(ref.child, parent);
|
|
1360
1716
|
} catch (err) {
|
|
1361
1717
|
report.recordError(err, ref.where);
|
|
1362
1718
|
}
|
|
@@ -1417,7 +1773,9 @@ class GexfReader implements XmlHandler {
|
|
|
1417
1773
|
const { where } = this.edgeFrame;
|
|
1418
1774
|
where.line = line;
|
|
1419
1775
|
where.element = idText ?? edgeElement(sourceText, targetText);
|
|
1776
|
+
this.countInSection("edge");
|
|
1420
1777
|
this.checkAttributeNames(attrs, EDGE_ATTRIBUTES, "edge", where);
|
|
1778
|
+
// source="" names the node whose id is "" (legal in GEXF), so only a missing endpoint is E_MISSING_ENDPOINT
|
|
1421
1779
|
if (sourceText === undefined || targetText === undefined) {
|
|
1422
1780
|
report.error(
|
|
1423
1781
|
"missing-value",
|
|
@@ -1444,18 +1802,15 @@ class GexfReader implements XmlHandler {
|
|
|
1444
1802
|
}
|
|
1445
1803
|
kind = type as EdgeKind;
|
|
1446
1804
|
}
|
|
1447
|
-
if (idText !== undefined) {
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
return;
|
|
1457
|
-
}
|
|
1458
|
-
this.edgeIds.add(idText);
|
|
1805
|
+
if (idText !== undefined && this.edgeIds.has(idText)) {
|
|
1806
|
+
report.error(
|
|
1807
|
+
"validation-error",
|
|
1808
|
+
DUPLICATE_EDGE_ID_CODE,
|
|
1809
|
+
`edge id "${idText}" is declared more than once; the edge is skipped`,
|
|
1810
|
+
where,
|
|
1811
|
+
);
|
|
1812
|
+
report.counts.skippedEdges++;
|
|
1813
|
+
return;
|
|
1459
1814
|
}
|
|
1460
1815
|
let index: number;
|
|
1461
1816
|
try {
|
|
@@ -1482,9 +1837,14 @@ class GexfReader implements XmlHandler {
|
|
|
1482
1837
|
report.counts.skippedEdges++;
|
|
1483
1838
|
return;
|
|
1484
1839
|
}
|
|
1840
|
+
// the id is claimed only by an edge that was added, so a rejected edge does not cost the next
|
|
1841
|
+
if (idText !== undefined) {
|
|
1842
|
+
this.edgeIds.add(idText);
|
|
1843
|
+
}
|
|
1485
1844
|
const frame = this.edgeFrame;
|
|
1486
1845
|
frame.index = index;
|
|
1487
1846
|
frame.mirror = this.resolver.lastMirror;
|
|
1847
|
+
frame.xmlWeight = attrs.get("weight") ?? null;
|
|
1488
1848
|
this.edge = frame;
|
|
1489
1849
|
try {
|
|
1490
1850
|
if (idText !== undefined) {
|
|
@@ -1525,7 +1885,28 @@ class GexfReader implements XmlHandler {
|
|
|
1525
1885
|
);
|
|
1526
1886
|
return undefined;
|
|
1527
1887
|
}
|
|
1528
|
-
|
|
1888
|
+
// a blank weight="" is an absent weight (common/weights.ts), as a blank weight attvalue is
|
|
1889
|
+
const weight = parseWeightText(text, this.report);
|
|
1890
|
+
this.checkOverflow(text, where);
|
|
1891
|
+
return weight;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
/**
|
|
1895
|
+
* Report (once) a weight text too large for a double, stored as Infinity; `INF` itself is a
|
|
1896
|
+
* legal weight (design section 3.7) and is not reported.
|
|
1897
|
+
* @param text - the weight text
|
|
1898
|
+
* @param where - the location
|
|
1899
|
+
*/
|
|
1900
|
+
private checkOverflow(text: string, where: IssueLocation): void {
|
|
1901
|
+
if (overflowsToInfinity(text)) {
|
|
1902
|
+
this.report.warnOnce(
|
|
1903
|
+
"precision",
|
|
1904
|
+
PRECISION_CODE,
|
|
1905
|
+
`weight ${text.trim()} is beyond the double range and is stored as Infinity`,
|
|
1906
|
+
where,
|
|
1907
|
+
`${PRECISION_CODE}:weight`,
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1529
1910
|
}
|
|
1530
1911
|
|
|
1531
1912
|
/**
|
|
@@ -1534,8 +1915,15 @@ class GexfReader implements XmlHandler {
|
|
|
1534
1915
|
* @param local - its local name
|
|
1535
1916
|
* @param attrs - its attributes
|
|
1536
1917
|
* @param line - the line
|
|
1918
|
+
* @param ns - the element's namespace
|
|
1537
1919
|
*/
|
|
1538
|
-
private startInEdge(
|
|
1920
|
+
private startInEdge(
|
|
1921
|
+
name: string,
|
|
1922
|
+
local: string,
|
|
1923
|
+
attrs: ReadonlyMap<string, string>,
|
|
1924
|
+
line: number,
|
|
1925
|
+
ns: string | null,
|
|
1926
|
+
): void {
|
|
1539
1927
|
const { edge } = this;
|
|
1540
1928
|
if (edge === null) {
|
|
1541
1929
|
this.ctx.push(Ctx.Skip);
|
|
@@ -1543,6 +1931,7 @@ class GexfReader implements XmlHandler {
|
|
|
1543
1931
|
}
|
|
1544
1932
|
switch (local) {
|
|
1545
1933
|
case "attvalues":
|
|
1934
|
+
this.attvalueIds.clear();
|
|
1546
1935
|
this.ctx.push(Ctx.Attvalues);
|
|
1547
1936
|
return;
|
|
1548
1937
|
case "spells":
|
|
@@ -1554,6 +1943,10 @@ class GexfReader implements XmlHandler {
|
|
|
1554
1943
|
case "color":
|
|
1555
1944
|
case "thickness":
|
|
1556
1945
|
case "shape":
|
|
1946
|
+
if (!isViz(name, ns)) {
|
|
1947
|
+
this.unknownElement(name, line);
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1557
1950
|
this.writeViz(name, local, attrs, "edge", edge.index, edge.where);
|
|
1558
1951
|
this.ctx.push(Ctx.Skip);
|
|
1559
1952
|
return;
|
|
@@ -1578,6 +1971,7 @@ class GexfReader implements XmlHandler {
|
|
|
1578
1971
|
return;
|
|
1579
1972
|
}
|
|
1580
1973
|
this.ctx.push(Ctx.Skip);
|
|
1974
|
+
this.checkAttributes("attvalue", attrs, line);
|
|
1581
1975
|
const { report, edge } = this;
|
|
1582
1976
|
const domain: "node" | "edge" = edge === null ? "node" : "edge";
|
|
1583
1977
|
let index: number;
|
|
@@ -1611,6 +2005,17 @@ class GexfReader implements XmlHandler {
|
|
|
1611
2005
|
);
|
|
1612
2006
|
return;
|
|
1613
2007
|
}
|
|
2008
|
+
if (!isTimed(attrs)) {
|
|
2009
|
+
if (this.attvalueIds.has(forId)) {
|
|
2010
|
+
report.warning(
|
|
2011
|
+
"validation-error",
|
|
2012
|
+
DUPLICATE_ATTRIBUTE_CODE,
|
|
2013
|
+
`a second static <attvalue for="${forId}"> on one ${domain}; the later value "${value}" is kept`,
|
|
2014
|
+
{ ...valueWhere, element: forId },
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
this.attvalueIds.add(forId);
|
|
2018
|
+
}
|
|
1614
2019
|
try {
|
|
1615
2020
|
if (domain === "edge" && this.weightAttr !== null && this.weightAttr.id === forId) {
|
|
1616
2021
|
if (isTimed(attrs)) {
|
|
@@ -1656,12 +2061,19 @@ class GexfReader implements XmlHandler {
|
|
|
1656
2061
|
if (edge === null) {
|
|
1657
2062
|
return;
|
|
1658
2063
|
}
|
|
1659
|
-
const weight = parseWeightText(text);
|
|
2064
|
+
const weight = parseWeightText(text, this.report);
|
|
1660
2065
|
if (weight === undefined) {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
2066
|
+
// blank is absent (common/weights.ts), as a blank weight="" XML attribute is
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
this.checkOverflow(text, where);
|
|
2070
|
+
if (edge.xmlWeight !== null) {
|
|
2071
|
+
this.report.warning(
|
|
2072
|
+
"validation-error",
|
|
2073
|
+
DUPLICATE_ATTRIBUTE_CODE,
|
|
2074
|
+
`the edge has both weight="${edge.xmlWeight}" and a weight attvalue "${text}"; the attvalue is kept`,
|
|
2075
|
+
where,
|
|
2076
|
+
);
|
|
1665
2077
|
}
|
|
1666
2078
|
sink.setEdgeWeight(edge.index, weight);
|
|
1667
2079
|
if (edge.mirror !== INVALID_INDEX) {
|
|
@@ -1705,10 +2117,44 @@ class GexfReader implements XmlHandler {
|
|
|
1705
2117
|
`long value ${text.trim()} of "${attribute.name}" exceeds 2^53 and was rounded`,
|
|
1706
2118
|
where,
|
|
1707
2119
|
);
|
|
2120
|
+
} else if (typeof value === "number" && overflowsToInfinity(text)) {
|
|
2121
|
+
this.report.warnOnce(
|
|
2122
|
+
"precision",
|
|
2123
|
+
PRECISION_CODE,
|
|
2124
|
+
`values of "${attribute.name}" beyond the double range (${text.trim()}) are stored as Infinity`,
|
|
2125
|
+
where,
|
|
2126
|
+
`${PRECISION_CODE}:overflow:${attribute.name}`,
|
|
2127
|
+
);
|
|
1708
2128
|
}
|
|
2129
|
+
this.checkOptions(attribute, value, where);
|
|
1709
2130
|
this.setCell(domain, attribute.handle, index, value);
|
|
1710
2131
|
}
|
|
1711
2132
|
|
|
2133
|
+
/**
|
|
2134
|
+
* Report (once per attribute) a value outside the `<options>` the attribute declares; the
|
|
2135
|
+
* value is kept.
|
|
2136
|
+
* @param attribute - the declaration
|
|
2137
|
+
* @param value - the parsed value (a list checks each item)
|
|
2138
|
+
* @param where - the value's location
|
|
2139
|
+
*/
|
|
2140
|
+
private checkOptions(attribute: TrackedAttribute, value: unknown, where: IssueLocation): void {
|
|
2141
|
+
const { options } = attribute.valueDecl;
|
|
2142
|
+
if (options === undefined || options.length === 0) {
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
const items = Array.isArray(value) ? (value as unknown[]) : [value];
|
|
2146
|
+
const outside = items.find((item) => !options.includes(item));
|
|
2147
|
+
if (typeof outside === "string" || typeof outside === "number" || typeof outside === "boolean") {
|
|
2148
|
+
this.report.warnOnce(
|
|
2149
|
+
"validation-error",
|
|
2150
|
+
VALUE_OUTSIDE_OPTIONS_CODE,
|
|
2151
|
+
`"${outside}" is not among the options of attribute "${attribute.name}"; the value is kept`,
|
|
2152
|
+
where,
|
|
2153
|
+
`${VALUE_OUTSIDE_OPTIONS_CODE}:${attribute.name}`,
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
|
|
1712
2158
|
/**
|
|
1713
2159
|
* Append a timed value to the attribute's temporal table (design section 5.10), creating the
|
|
1714
2160
|
* table on first use.
|
|
@@ -1762,6 +2208,9 @@ class GexfReader implements XmlHandler {
|
|
|
1762
2208
|
}
|
|
1763
2209
|
}
|
|
1764
2210
|
const row = this.temporalRow(attrs, index, value, where);
|
|
2211
|
+
if (row === null) {
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
1765
2214
|
if (temporalValue) {
|
|
1766
2215
|
row.push(valueText ?? undefined);
|
|
1767
2216
|
}
|
|
@@ -1799,14 +2248,15 @@ class GexfReader implements XmlHandler {
|
|
|
1799
2248
|
),
|
|
1800
2249
|
);
|
|
1801
2250
|
}
|
|
1802
|
-
const value = parseWeightText(text);
|
|
2251
|
+
const value = parseWeightText(text, this.report);
|
|
1803
2252
|
if (value === undefined) {
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
2253
|
+
// blank is absent (common/weights.ts): no timed value
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
const row = this.temporalRow(attrs, index, value, where);
|
|
2257
|
+
if (row !== null) {
|
|
2258
|
+
sink.addExtensionRow(weightAttr.table, row);
|
|
1808
2259
|
}
|
|
1809
|
-
sink.addExtensionRow(weightAttr.table, this.temporalRow(attrs, index, value, where));
|
|
1810
2260
|
}
|
|
1811
2261
|
|
|
1812
2262
|
/**
|
|
@@ -1815,15 +2265,18 @@ class GexfReader implements XmlHandler {
|
|
|
1815
2265
|
* @param index - the element's index
|
|
1816
2266
|
* @param value - the parsed value
|
|
1817
2267
|
* @param where - the value's location
|
|
1818
|
-
* @returns the row, without the optional value text
|
|
2268
|
+
* @returns the row, without the optional value text; null when the interval is inverted (recorded)
|
|
1819
2269
|
*/
|
|
1820
2270
|
private temporalRow(
|
|
1821
2271
|
attrs: ReadonlyMap<string, string>,
|
|
1822
2272
|
index: number,
|
|
1823
2273
|
value: unknown,
|
|
1824
2274
|
where: IssueLocation,
|
|
1825
|
-
): unknown[] {
|
|
2275
|
+
): unknown[] | null {
|
|
1826
2276
|
const bounds = this.readBounds(attrs, where);
|
|
2277
|
+
if (bounds === null) {
|
|
2278
|
+
return null;
|
|
2279
|
+
}
|
|
1827
2280
|
return [
|
|
1828
2281
|
index,
|
|
1829
2282
|
bounds.start.value,
|
|
@@ -1841,12 +2294,12 @@ class GexfReader implements XmlHandler {
|
|
|
1841
2294
|
* section 5.1); missing bounds are unbounded.
|
|
1842
2295
|
* @param attrs - the attributes
|
|
1843
2296
|
* @param where - the location, for the conflict warning
|
|
1844
|
-
* @returns the bounds and the open bits
|
|
2297
|
+
* @returns the bounds and the open bits; null (recorded as E_BAD_VALUE) when the interval is inverted
|
|
1845
2298
|
*/
|
|
1846
2299
|
private readBounds(
|
|
1847
2300
|
attrs: ReadonlyMap<string, string>,
|
|
1848
2301
|
where: IssueLocation,
|
|
1849
|
-
): { start: TemporalValue; end: TemporalValue; open: number } {
|
|
2302
|
+
): { start: TemporalValue; end: TemporalValue; open: number } | null {
|
|
1850
2303
|
let start: TemporalValue = { value: -Infinity, text: null };
|
|
1851
2304
|
let end: TemporalValue = { value: Infinity, text: null };
|
|
1852
2305
|
let open = 0;
|
|
@@ -1854,6 +2307,14 @@ class GexfReader implements XmlHandler {
|
|
|
1854
2307
|
if (timestamp !== undefined) {
|
|
1855
2308
|
start = parseTimeText(timestamp, this.timeFormat);
|
|
1856
2309
|
end = start;
|
|
2310
|
+
if (attrs.has("start") || attrs.has("end") || attrs.has("startopen") || attrs.has("endopen")) {
|
|
2311
|
+
this.report.warnOnce(
|
|
2312
|
+
"validation-error",
|
|
2313
|
+
TIMESTAMP_CONFLICT_CODE,
|
|
2314
|
+
"both timestamp and start / end are given; start / end set the interval and the timestamp is kept as written",
|
|
2315
|
+
where,
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
1857
2318
|
}
|
|
1858
2319
|
const startText = attrs.get("start");
|
|
1859
2320
|
const startOpen = attrs.get("startopen");
|
|
@@ -1877,6 +2338,15 @@ class GexfReader implements XmlHandler {
|
|
|
1877
2338
|
end = parseTimeText(endOpen, this.timeFormat);
|
|
1878
2339
|
open |= OPEN_END;
|
|
1879
2340
|
}
|
|
2341
|
+
if (start.value > end.value) {
|
|
2342
|
+
this.report.error(
|
|
2343
|
+
"validation-error",
|
|
2344
|
+
BAD_VALUE_CODE,
|
|
2345
|
+
`the interval starts after it ends (${start.text ?? String(start.value)} > ${end.text ?? String(end.value)}); it is not kept`,
|
|
2346
|
+
where,
|
|
2347
|
+
);
|
|
2348
|
+
return null;
|
|
2349
|
+
}
|
|
1880
2350
|
return { start, end, open };
|
|
1881
2351
|
}
|
|
1882
2352
|
|
|
@@ -1919,9 +2389,13 @@ class GexfReader implements XmlHandler {
|
|
|
1919
2389
|
attrs.has("endopen")
|
|
1920
2390
|
) {
|
|
1921
2391
|
const bounds = this.readBounds(attrs, where);
|
|
2392
|
+
if (bounds === null) {
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
1922
2395
|
const timestamp = attrs.get("timestamp");
|
|
1923
2396
|
if (timestamp !== undefined) {
|
|
1924
|
-
|
|
2397
|
+
// parsed again: beside start / startopen, bounds.start is the start, not the timestamp
|
|
2398
|
+
columns.timestamp.set(sink, report, index, parseTimeText(timestamp, this.timeFormat), where);
|
|
1925
2399
|
}
|
|
1926
2400
|
if (attrs.has("start") || attrs.has("startopen")) {
|
|
1927
2401
|
columns.start.set(sink, report, index, bounds.start, where);
|
|
@@ -1969,10 +2443,20 @@ class GexfReader implements XmlHandler {
|
|
|
1969
2443
|
line: where.line ?? null,
|
|
1970
2444
|
});
|
|
1971
2445
|
}
|
|
1972
|
-
|
|
2446
|
+
const pair = [
|
|
1973
2447
|
parseTimeText(items[0], this.timeFormat).value,
|
|
1974
2448
|
parseTimeText(items[1], this.timeFormat).value,
|
|
1975
|
-
]
|
|
2449
|
+
];
|
|
2450
|
+
if (pair[0] > pair[1]) {
|
|
2451
|
+
this.report.error(
|
|
2452
|
+
"validation-error",
|
|
2453
|
+
BAD_VALUE_CODE,
|
|
2454
|
+
`interval "${part.trim()}" starts after it ends; it is not kept`,
|
|
2455
|
+
where,
|
|
2456
|
+
);
|
|
2457
|
+
continue;
|
|
2458
|
+
}
|
|
2459
|
+
pairs.push(pair);
|
|
1976
2460
|
}
|
|
1977
2461
|
return pairs;
|
|
1978
2462
|
}
|
|
@@ -1990,9 +2474,13 @@ class GexfReader implements XmlHandler {
|
|
|
1990
2474
|
return;
|
|
1991
2475
|
}
|
|
1992
2476
|
this.ctx.push(Ctx.Skip);
|
|
2477
|
+
this.checkAttributes("spell", attrs, line);
|
|
1993
2478
|
const where = this.spellsWhere ?? { line };
|
|
1994
2479
|
try {
|
|
1995
2480
|
const bounds = this.readBounds(attrs, where);
|
|
2481
|
+
if (bounds === null) {
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
1996
2484
|
this.spellPairs?.push([bounds.start.value, bounds.end.value]);
|
|
1997
2485
|
this.spellOpen.push(bounds.open);
|
|
1998
2486
|
} catch (err) {
|
|
@@ -2123,34 +2611,59 @@ class GexfReader implements XmlHandler {
|
|
|
2123
2611
|
}
|
|
2124
2612
|
|
|
2125
2613
|
/**
|
|
2126
|
-
* Report XML attributes the importer does not read (once per attribute name and
|
|
2614
|
+
* Report XML attributes the importer does not read (once per attribute name and element kind;
|
|
2615
|
+
* beyond MAX_UNKNOWN_ATTRIBUTE_NAMES distinct names per kind the rest are counted and reported
|
|
2616
|
+
* once at the end). Namespace declarations and `xml:` / `xsi:` attributes are not data.
|
|
2127
2617
|
* @param attrs - the element's attributes
|
|
2128
2618
|
* @param known - the attribute names read
|
|
2129
|
-
* @param
|
|
2619
|
+
* @param kind - the element name
|
|
2130
2620
|
* @param where - the element's location
|
|
2131
2621
|
*/
|
|
2132
2622
|
private checkAttributeNames(
|
|
2133
2623
|
attrs: ReadonlyMap<string, string>,
|
|
2134
2624
|
known: ReadonlySet<string>,
|
|
2135
|
-
|
|
2625
|
+
kind: string,
|
|
2136
2626
|
where: IssueLocation,
|
|
2137
2627
|
): void {
|
|
2628
|
+
if (attrs.size === 0) {
|
|
2629
|
+
return;
|
|
2630
|
+
}
|
|
2138
2631
|
for (const key of attrs.keys()) {
|
|
2139
|
-
if (
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2632
|
+
if (known.has(key) || key === "xmlns" || /^(xmlns|xml|xsi):/.test(key)) {
|
|
2633
|
+
continue;
|
|
2634
|
+
}
|
|
2635
|
+
let names = this.unknownNames.get(kind);
|
|
2636
|
+
if (names === undefined) {
|
|
2637
|
+
names = new Set();
|
|
2638
|
+
this.unknownNames.set(kind, names);
|
|
2639
|
+
}
|
|
2640
|
+
if (names.has(key)) {
|
|
2641
|
+
continue;
|
|
2642
|
+
}
|
|
2643
|
+
names.add(key);
|
|
2644
|
+
if (names.size > MAX_UNKNOWN_ATTRIBUTE_NAMES) {
|
|
2645
|
+
this.unknownOverflow.set(kind, (this.unknownOverflow.get(kind) ?? 0) + 1);
|
|
2646
|
+
continue;
|
|
2150
2647
|
}
|
|
2648
|
+
this.report.warning(
|
|
2649
|
+
"unsupported",
|
|
2650
|
+
UNKNOWN_XML_ATTRIBUTE_CODE,
|
|
2651
|
+
`attribute ${key} of <${kind}> is not GEXF and was ignored`,
|
|
2652
|
+
{ ...where, element: key },
|
|
2653
|
+
);
|
|
2151
2654
|
}
|
|
2152
2655
|
}
|
|
2153
2656
|
|
|
2657
|
+
/**
|
|
2658
|
+
* Report the XML attributes the importer does not read on an element other than node / edge.
|
|
2659
|
+
* @param kind - the element's local name (a key of ELEMENT_ATTRIBUTES)
|
|
2660
|
+
* @param attrs - its attributes
|
|
2661
|
+
* @param line - the line
|
|
2662
|
+
*/
|
|
2663
|
+
private checkAttributes(kind: string, attrs: ReadonlyMap<string, string>, line: number): void {
|
|
2664
|
+
this.checkAttributeNames(attrs, ELEMENT_ATTRIBUTES[kind] ?? new Set(), kind, { line });
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2154
2667
|
/**
|
|
2155
2668
|
* Write one cell of a domain.
|
|
2156
2669
|
* @param domain - node or edge
|
|
@@ -2195,6 +2708,17 @@ class GexfReader implements XmlHandler {
|
|
|
2195
2708
|
}
|
|
2196
2709
|
}
|
|
2197
2710
|
|
|
2711
|
+
/**
|
|
2712
|
+
* Whether a node / edge child named like a viz element is one: in a viz namespace, or written
|
|
2713
|
+
* with the `viz:` prefix the document never declares (hand-written files often omit it).
|
|
2714
|
+
* @param name - the element name as written
|
|
2715
|
+
* @param ns - its namespace
|
|
2716
|
+
* @returns true for a viz element
|
|
2717
|
+
*/
|
|
2718
|
+
function isViz(name: string, ns: string | null): boolean {
|
|
2719
|
+
return isVizNamespace(ns) || (ns === null && name.startsWith("viz:"));
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2198
2722
|
/**
|
|
2199
2723
|
* The element text of an edge without an id, for issues.
|
|
2200
2724
|
* @param source - the source text, or undefined
|
|
@@ -2267,7 +2791,7 @@ function parseColor(attrs: ReadonlyMap<string, string>): number[] {
|
|
|
2267
2791
|
if (hex !== undefined && !attrs.has("r")) {
|
|
2268
2792
|
const digits = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
2269
2793
|
if (!/^[0-9a-fA-F]{6}$/.test(digits)) {
|
|
2270
|
-
throw new GraphFormatError("E_COLUMN_TYPE", `viz hex="${hex}" is not a #RRGGBB
|
|
2794
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `viz hex="${hex}" is not a #RRGGBB color`, {
|
|
2271
2795
|
attribute: "hex",
|
|
2272
2796
|
});
|
|
2273
2797
|
}
|
|
@@ -2293,7 +2817,7 @@ const XML_START = /^\uFEFF?\s*</;
|
|
|
2293
2817
|
* not start with markup
|
|
2294
2818
|
*/
|
|
2295
2819
|
function sniffGexf(head: Uint8Array): number {
|
|
2296
|
-
const text =
|
|
2820
|
+
const text = sniffXmlText(head);
|
|
2297
2821
|
// Only a document that starts as markup: a JSON or CSV value may mention a `<gexf>` tag.
|
|
2298
2822
|
if (!XML_START.test(text)) {
|
|
2299
2823
|
return 0;
|
|
@@ -2323,9 +2847,13 @@ function resolveGexfOptions(options: (GexfImportOptions & CommonImportOptions) |
|
|
|
2323
2847
|
return viz;
|
|
2324
2848
|
}
|
|
2325
2849
|
|
|
2326
|
-
/**
|
|
2850
|
+
/**
|
|
2851
|
+
* The GEXF importer.
|
|
2852
|
+
* @category Built-in formats
|
|
2853
|
+
*/
|
|
2327
2854
|
export const gexfImporter: GraphImporter<GexfImportOptions> = Object.freeze({
|
|
2328
2855
|
format: GEXF_FORMAT,
|
|
2856
|
+
options: Object.freeze(["viz"]),
|
|
2329
2857
|
extensions: Object.freeze([".gexf"]),
|
|
2330
2858
|
mimeTypes: Object.freeze(["application/gexf+xml", "application/xml", "text/xml"]),
|
|
2331
2859
|
sniff: sniffGexf,
|
|
@@ -2355,10 +2883,13 @@ export const gexfImporter: GraphImporter<GexfImportOptions> = Object.freeze({
|
|
|
2355
2883
|
const reader = new GexfReader(sink, report, resolved, viz);
|
|
2356
2884
|
try {
|
|
2357
2885
|
await tokenizeXml(
|
|
2358
|
-
textChunks(input, report, { ...resolved, declaredEncoding: xmlDeclaredEncoding }),
|
|
2886
|
+
textChunks(input, report, { ...resolved, declaredEncoding: xmlDeclaredEncoding, xml: true }),
|
|
2359
2887
|
reader,
|
|
2360
2888
|
);
|
|
2361
2889
|
} catch (err) {
|
|
2890
|
+
if (err instanceof XmlEmptyInputError) {
|
|
2891
|
+
report.fail(EMPTY_INPUT_CODE, "the input is empty (no markup at all)", { line: err.line });
|
|
2892
|
+
}
|
|
2362
2893
|
if (err instanceof XmlSyntaxError) {
|
|
2363
2894
|
report.fail(XML_SYNTAX_CODE, err.message, { line: err.line });
|
|
2364
2895
|
}
|