@graphty/graph-io 0.0.0 → 0.2.1
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/LICENSE +21 -0
- package/README.md +250 -28
- package/dist/chunks/children-CL3Cy0ez.js +238 -0
- package/dist/chunks/children-CL3Cy0ez.js.map +1 -0
- package/dist/chunks/escape-DyI8JofU.js +938 -0
- package/dist/chunks/escape-DyI8JofU.js.map +1 -0
- package/dist/chunks/importer-CQnJuWJw.js +2987 -0
- package/dist/chunks/importer-CQnJuWJw.js.map +1 -0
- package/dist/chunks/importer-CpCpfbxr.js +2015 -0
- package/dist/chunks/importer-CpCpfbxr.js.map +1 -0
- package/dist/chunks/importer-DbnGYr3_.js +2342 -0
- package/dist/chunks/importer-DbnGYr3_.js.map +1 -0
- package/dist/chunks/importer-GozH8DkN.js +3050 -0
- package/dist/chunks/importer-GozH8DkN.js.map +1 -0
- package/dist/chunks/records-CGpxszm1.js +605 -0
- package/dist/chunks/records-CGpxszm1.js.map +1 -0
- package/dist/chunks/text-CajMdVFy.js +189 -0
- package/dist/chunks/text-CajMdVFy.js.map +1 -0
- package/dist/chunks/writer-DxSKC7TL.js +2842 -0
- package/dist/chunks/writer-DxSKC7TL.js.map +1 -0
- package/dist/csv.d.ts +1 -0
- package/dist/csv.js +1702 -0
- package/dist/csv.js.map +1 -0
- package/dist/dot.d.ts +1 -0
- package/dist/dot.js +8 -0
- package/dist/dot.js.map +1 -0
- package/dist/gexf.d.ts +1 -0
- package/dist/gexf.js +3466 -0
- package/dist/gexf.js.map +1 -0
- package/dist/gml.d.ts +1 -0
- package/dist/gml.js +2647 -0
- package/dist/gml.js.map +1 -0
- package/dist/graph-io.d.ts +1 -0
- package/dist/graph-io.js +790 -0
- package/dist/graph-io.js.map +1 -0
- package/dist/graphml.d.ts +1 -0
- package/dist/graphml.js +8 -0
- package/dist/graphml.js.map +1 -0
- package/dist/json.d.ts +1 -0
- package/dist/json.js +11 -0
- package/dist/json.js.map +1 -0
- package/dist/neo4j.d.ts +1 -0
- package/dist/neo4j.js +2046 -0
- package/dist/neo4j.js.map +1 -0
- package/dist/pajek.d.ts +1 -0
- package/dist/pajek.js +8 -0
- package/dist/pajek.js.map +1 -0
- package/dist/src/children.d.ts +134 -0
- package/dist/src/children.d.ts.map +1 -0
- package/dist/src/children.js +274 -0
- package/dist/src/children.js.map +1 -0
- package/dist/src/common/attributes.d.ts +229 -0
- package/dist/src/common/attributes.d.ts.map +1 -0
- package/dist/src/common/attributes.js +368 -0
- package/dist/src/common/attributes.js.map +1 -0
- package/dist/src/common/codes.d.ts +105 -0
- package/dist/src/common/codes.d.ts.map +1 -0
- package/dist/src/common/codes.js +107 -0
- package/dist/src/common/codes.js.map +1 -0
- package/dist/src/common/declared-types.d.ts +84 -0
- package/dist/src/common/declared-types.d.ts.map +1 -0
- package/dist/src/common/declared-types.js +326 -0
- package/dist/src/common/declared-types.js.map +1 -0
- package/dist/src/common/direction.d.ts +206 -0
- package/dist/src/common/direction.d.ts.map +1 -0
- package/dist/src/common/direction.js +370 -0
- package/dist/src/common/direction.js.map +1 -0
- package/dist/src/common/escape.d.ts +92 -0
- package/dist/src/common/escape.d.ts.map +1 -0
- package/dist/src/common/escape.js +212 -0
- package/dist/src/common/escape.js.map +1 -0
- package/dist/src/common/export.d.ts +249 -0
- package/dist/src/common/export.d.ts.map +1 -0
- package/dist/src/common/export.js +594 -0
- package/dist/src/common/export.js.map +1 -0
- package/dist/src/common/format.d.ts +59 -0
- package/dist/src/common/format.d.ts.map +1 -0
- package/dist/src/common/format.js +106 -0
- package/dist/src/common/format.js.map +1 -0
- package/dist/src/common/ids.d.ts +83 -0
- package/dist/src/common/ids.d.ts.map +1 -0
- package/dist/src/common/ids.js +158 -0
- package/dist/src/common/ids.js.map +1 -0
- package/dist/src/common/input.d.ts +100 -0
- package/dist/src/common/input.d.ts.map +1 -0
- package/dist/src/common/input.js +335 -0
- package/dist/src/common/input.js.map +1 -0
- package/dist/src/common/lists.d.ts +34 -0
- package/dist/src/common/lists.d.ts.map +1 -0
- package/dist/src/common/lists.js +185 -0
- package/dist/src/common/lists.js.map +1 -0
- package/dist/src/common/options.d.ts +108 -0
- package/dist/src/common/options.d.ts.map +1 -0
- package/dist/src/common/options.js +265 -0
- package/dist/src/common/options.js.map +1 -0
- package/dist/src/common/report.d.ts +187 -0
- package/dist/src/common/report.d.ts.map +1 -0
- package/dist/src/common/report.js +274 -0
- package/dist/src/common/report.js.map +1 -0
- package/dist/src/common/temporal.d.ts +71 -0
- package/dist/src/common/temporal.d.ts.map +1 -0
- package/dist/src/common/temporal.js +266 -0
- package/dist/src/common/temporal.js.map +1 -0
- package/dist/src/common/text.d.ts +104 -0
- package/dist/src/common/text.d.ts.map +1 -0
- package/dist/src/common/text.js +255 -0
- package/dist/src/common/text.js.map +1 -0
- package/dist/src/common/weights.d.ts +77 -0
- package/dist/src/common/weights.d.ts.map +1 -0
- package/dist/src/common/weights.js +156 -0
- package/dist/src/common/weights.js.map +1 -0
- package/dist/src/common/writer.d.ts +51 -0
- package/dist/src/common/writer.d.ts.map +1 -0
- package/dist/src/common/writer.js +108 -0
- package/dist/src/common/writer.js.map +1 -0
- package/dist/src/common/xml.d.ts +245 -0
- package/dist/src/common/xml.d.ts.map +1 -0
- package/dist/src/common/xml.js +942 -0
- package/dist/src/common/xml.js.map +1 -0
- package/dist/src/formats/csv/exporter.d.ts +70 -0
- package/dist/src/formats/csv/exporter.d.ts.map +1 -0
- package/dist/src/formats/csv/exporter.js +682 -0
- package/dist/src/formats/csv/exporter.js.map +1 -0
- package/dist/src/formats/csv/header.d.ts +66 -0
- package/dist/src/formats/csv/header.d.ts.map +1 -0
- package/dist/src/formats/csv/header.js +152 -0
- package/dist/src/formats/csv/header.js.map +1 -0
- package/dist/src/formats/csv/importer.d.ts +82 -0
- package/dist/src/formats/csv/importer.d.ts.map +1 -0
- package/dist/src/formats/csv/importer.js +849 -0
- package/dist/src/formats/csv/importer.js.map +1 -0
- package/dist/src/formats/csv/index.d.ts +60 -0
- package/dist/src/formats/csv/index.d.ts.map +1 -0
- package/dist/src/formats/csv/index.js +63 -0
- package/dist/src/formats/csv/index.js.map +1 -0
- package/dist/src/formats/csv/records.d.ts +188 -0
- package/dist/src/formats/csv/records.d.ts.map +1 -0
- package/dist/src/formats/csv/records.js +702 -0
- package/dist/src/formats/csv/records.js.map +1 -0
- package/dist/src/formats/csv/values.d.ts +105 -0
- package/dist/src/formats/csv/values.d.ts.map +1 -0
- package/dist/src/formats/csv/values.js +192 -0
- package/dist/src/formats/csv/values.js.map +1 -0
- package/dist/src/formats/dot/exporter.d.ts +52 -0
- package/dist/src/formats/dot/exporter.d.ts.map +1 -0
- package/dist/src/formats/dot/exporter.js +836 -0
- package/dist/src/formats/dot/exporter.js.map +1 -0
- package/dist/src/formats/dot/importer.d.ts +102 -0
- package/dist/src/formats/dot/importer.d.ts.map +1 -0
- package/dist/src/formats/dot/importer.js +1291 -0
- package/dist/src/formats/dot/importer.js.map +1 -0
- package/dist/src/formats/dot/index.d.ts +7 -0
- package/dist/src/formats/dot/index.d.ts.map +1 -0
- package/dist/src/formats/dot/index.js +7 -0
- package/dist/src/formats/dot/index.js.map +1 -0
- package/dist/src/formats/dot/names.d.ts +29 -0
- package/dist/src/formats/dot/names.d.ts.map +1 -0
- package/dist/src/formats/dot/names.js +28 -0
- package/dist/src/formats/dot/names.js.map +1 -0
- package/dist/src/formats/dot/tokenizer.d.ts +114 -0
- package/dist/src/formats/dot/tokenizer.d.ts.map +1 -0
- package/dist/src/formats/dot/tokenizer.js +341 -0
- package/dist/src/formats/dot/tokenizer.js.map +1 -0
- package/dist/src/formats/gexf/exporter.d.ts +56 -0
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -0
- package/dist/src/formats/gexf/exporter.js +1395 -0
- package/dist/src/formats/gexf/exporter.js.map +1 -0
- package/dist/src/formats/gexf/importer.d.ts +73 -0
- package/dist/src/formats/gexf/importer.d.ts.map +1 -0
- package/dist/src/formats/gexf/importer.js +1880 -0
- package/dist/src/formats/gexf/importer.js.map +1 -0
- package/dist/src/formats/gexf/index.d.ts +96 -0
- package/dist/src/formats/gexf/index.d.ts.map +1 -0
- package/dist/src/formats/gexf/index.js +97 -0
- package/dist/src/formats/gexf/index.js.map +1 -0
- package/dist/src/formats/gexf/schema.d.ts +135 -0
- package/dist/src/formats/gexf/schema.d.ts.map +1 -0
- package/dist/src/formats/gexf/schema.js +323 -0
- package/dist/src/formats/gexf/schema.js.map +1 -0
- package/dist/src/formats/gml/exporter.d.ts +69 -0
- package/dist/src/formats/gml/exporter.d.ts.map +1 -0
- package/dist/src/formats/gml/exporter.js +1093 -0
- package/dist/src/formats/gml/exporter.js.map +1 -0
- package/dist/src/formats/gml/importer.d.ts +66 -0
- package/dist/src/formats/gml/importer.d.ts.map +1 -0
- package/dist/src/formats/gml/importer.js +1331 -0
- package/dist/src/formats/gml/importer.js.map +1 -0
- package/dist/src/formats/gml/index.d.ts +85 -0
- package/dist/src/formats/gml/index.d.ts.map +1 -0
- package/dist/src/formats/gml/index.js +88 -0
- package/dist/src/formats/gml/index.js.map +1 -0
- package/dist/src/formats/gml/syntax.d.ts +186 -0
- package/dist/src/formats/gml/syntax.d.ts.map +1 -0
- package/dist/src/formats/gml/syntax.js +467 -0
- package/dist/src/formats/gml/syntax.js.map +1 -0
- package/dist/src/formats/graphml/constants.d.ts +169 -0
- package/dist/src/formats/graphml/constants.d.ts.map +1 -0
- package/dist/src/formats/graphml/constants.js +165 -0
- package/dist/src/formats/graphml/constants.js.map +1 -0
- package/dist/src/formats/graphml/exporter.d.ts +34 -0
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -0
- package/dist/src/formats/graphml/exporter.js +1176 -0
- package/dist/src/formats/graphml/exporter.js.map +1 -0
- package/dist/src/formats/graphml/importer.d.ts +31 -0
- package/dist/src/formats/graphml/importer.d.ts.map +1 -0
- package/dist/src/formats/graphml/importer.js +1607 -0
- package/dist/src/formats/graphml/importer.js.map +1 -0
- package/dist/src/formats/graphml/index.d.ts +8 -0
- package/dist/src/formats/graphml/index.d.ts.map +1 -0
- package/dist/src/formats/graphml/index.js +8 -0
- package/dist/src/formats/graphml/index.js.map +1 -0
- package/dist/src/formats/graphml/tree.d.ts +72 -0
- package/dist/src/formats/graphml/tree.d.ts.map +1 -0
- package/dist/src/formats/graphml/tree.js +290 -0
- package/dist/src/formats/graphml/tree.js.map +1 -0
- package/dist/src/formats/json/dialect.d.ts +125 -0
- package/dist/src/formats/json/dialect.d.ts.map +1 -0
- package/dist/src/formats/json/dialect.js +262 -0
- package/dist/src/formats/json/dialect.js.map +1 -0
- package/dist/src/formats/json/exporter.d.ts +89 -0
- package/dist/src/formats/json/exporter.d.ts.map +1 -0
- package/dist/src/formats/json/exporter.js +1358 -0
- package/dist/src/formats/json/exporter.js.map +1 -0
- package/dist/src/formats/json/importer.d.ts +108 -0
- package/dist/src/formats/json/importer.d.ts.map +1 -0
- package/dist/src/formats/json/importer.js +1838 -0
- package/dist/src/formats/json/importer.js.map +1 -0
- package/dist/src/formats/json/index.d.ts +8 -0
- package/dist/src/formats/json/index.d.ts.map +1 -0
- package/dist/src/formats/json/index.js +8 -0
- package/dist/src/formats/json/index.js.map +1 -0
- package/dist/src/formats/neo4j/exporter.d.ts +68 -0
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -0
- package/dist/src/formats/neo4j/exporter.js +1055 -0
- package/dist/src/formats/neo4j/exporter.js.map +1 -0
- package/dist/src/formats/neo4j/header.d.ts +52 -0
- package/dist/src/formats/neo4j/header.d.ts.map +1 -0
- package/dist/src/formats/neo4j/header.js +131 -0
- package/dist/src/formats/neo4j/header.js.map +1 -0
- package/dist/src/formats/neo4j/importer.d.ts +73 -0
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -0
- package/dist/src/formats/neo4j/importer.js +932 -0
- package/dist/src/formats/neo4j/importer.js.map +1 -0
- package/dist/src/formats/neo4j/index.d.ts +79 -0
- package/dist/src/formats/neo4j/index.d.ts.map +1 -0
- package/dist/src/formats/neo4j/index.js +83 -0
- package/dist/src/formats/neo4j/index.js.map +1 -0
- package/dist/src/formats/pajek/exporter.d.ts +58 -0
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -0
- package/dist/src/formats/pajek/exporter.js +825 -0
- package/dist/src/formats/pajek/exporter.js.map +1 -0
- package/dist/src/formats/pajek/importer.d.ts +88 -0
- package/dist/src/formats/pajek/importer.d.ts.map +1 -0
- package/dist/src/formats/pajek/importer.js +1047 -0
- package/dist/src/formats/pajek/importer.js.map +1 -0
- package/dist/src/formats/pajek/index.d.ts +7 -0
- package/dist/src/formats/pajek/index.d.ts.map +1 -0
- package/dist/src/formats/pajek/index.js +7 -0
- package/dist/src/formats/pajek/index.js.map +1 -0
- package/dist/src/formats/pajek/syntax.d.ts +112 -0
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -0
- package/dist/src/formats/pajek/syntax.js +269 -0
- package/dist/src/formats/pajek/syntax.js.map +1 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +39 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/registry.d.ts +207 -0
- package/dist/src/registry.d.ts.map +1 -0
- package/dist/src/registry.js +481 -0
- package/dist/src/registry.js.map +1 -0
- package/dist/src/sniff.d.ts +104 -0
- package/dist/src/sniff.d.ts.map +1 -0
- package/dist/src/sniff.js +357 -0
- package/dist/src/sniff.js.map +1 -0
- package/dist/src/types.d.ts +238 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +29 -0
- package/dist/src/types.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +122 -7
- package/src/children.ts +335 -0
- package/src/common/attributes.ts +520 -0
- package/src/common/codes.ts +153 -0
- package/src/common/declared-types.ts +374 -0
- package/src/common/direction.ts +518 -0
- package/src/common/escape.ts +231 -0
- package/src/common/export.ts +817 -0
- package/src/common/format.ts +111 -0
- package/src/common/ids.ts +176 -0
- package/src/common/input.ts +378 -0
- package/src/common/lists.ts +196 -0
- package/src/common/options.ts +377 -0
- package/src/common/report.ts +352 -0
- package/src/common/temporal.ts +302 -0
- package/src/common/text.ts +294 -0
- package/src/common/weights.ts +202 -0
- package/src/common/writer.ts +123 -0
- package/src/common/xml.ts +1053 -0
- package/src/formats/csv/exporter.ts +894 -0
- package/src/formats/csv/header.ts +172 -0
- package/src/formats/csv/importer.ts +1104 -0
- package/src/formats/csv/index.ts +88 -0
- package/src/formats/csv/records.ts +813 -0
- package/src/formats/csv/values.ts +224 -0
- package/src/formats/dot/exporter.ts +1014 -0
- package/src/formats/dot/importer.ts +1549 -0
- package/src/formats/dot/index.ts +7 -0
- package/src/formats/dot/names.ts +40 -0
- package/src/formats/dot/tokenizer.ts +384 -0
- package/src/formats/gexf/exporter.ts +1696 -0
- package/src/formats/gexf/importer.ts +2333 -0
- package/src/formats/gexf/index.ts +142 -0
- package/src/formats/gexf/schema.ts +361 -0
- package/src/formats/gml/exporter.ts +1404 -0
- package/src/formats/gml/importer.ts +1591 -0
- package/src/formats/gml/index.ts +128 -0
- package/src/formats/gml/syntax.ts +545 -0
- package/src/formats/graphml/constants.ts +225 -0
- package/src/formats/graphml/exporter.ts +1458 -0
- package/src/formats/graphml/importer.ts +2027 -0
- package/src/formats/graphml/index.ts +8 -0
- package/src/formats/graphml/tree.ts +318 -0
- package/src/formats/json/dialect.ts +317 -0
- package/src/formats/json/exporter.ts +1616 -0
- package/src/formats/json/importer.ts +2271 -0
- package/src/formats/json/index.ts +8 -0
- package/src/formats/neo4j/exporter.ts +1287 -0
- package/src/formats/neo4j/header.ts +156 -0
- package/src/formats/neo4j/importer.ts +1220 -0
- package/src/formats/neo4j/index.ts +116 -0
- package/src/formats/pajek/exporter.ts +1000 -0
- package/src/formats/pajek/importer.ts +1311 -0
- package/src/formats/pajek/index.ts +7 -0
- package/src/formats/pajek/syntax.ts +307 -0
- package/src/index.ts +244 -0
- package/src/registry.ts +617 -0
- package/src/sniff.ts +397 -0
- package/src/types.ts +262 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `@graphty/graph-io/graphml` subpath: the GraphML importer and exporter (design section 8.2),
|
|
3
|
+
* their option types and their issue and loss-note code tables.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { GRAPHML_ISSUE, GRAPHML_LOSS } from "./constants.js";
|
|
7
|
+
export { graphmlExporter, type GraphmlExportOptions } from "./exporter.js";
|
|
8
|
+
export { graphmlImporter, type GraphmlImportOptions } from "./importer.js";
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nested XML inside a GraphML `<data>` element (yFiles / yEd `y:ShapeNode`, `y:PolyLineEdge`,
|
|
3
|
+
* ...) as a JSON tree, the value of a `json` column with `origin.namespace: "yfiles"` (design
|
|
4
|
+
* section 8.5: structure preserved, not byte-exact). The shape follows the fast-xml-parser
|
|
5
|
+
* convention graphty-element's parser already navigates: an element is an object whose
|
|
6
|
+
* `@_<name>` keys are its attributes, whose `#text` key is its non-whitespace character data
|
|
7
|
+
* when it also has attributes or children, and whose other keys are its child elements (an
|
|
8
|
+
* array when a name repeats); an element with neither attributes nor children is its text
|
|
9
|
+
* (an empty string for an empty element).
|
|
10
|
+
*
|
|
11
|
+
* The builder consumes the tokenizer events of one subtree; the writer produces the XML text of
|
|
12
|
+
* a tree again, so a yFiles column re-exports as the same nested markup.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { GraphFormatError } from "@graphty/graph-format";
|
|
16
|
+
|
|
17
|
+
import { escapeXmlAttribute, escapeXmlText } from "../../common/escape.js";
|
|
18
|
+
import { isWhitespace, isXmlName } from "../../common/xml.js";
|
|
19
|
+
|
|
20
|
+
/** The prefix of an attribute key in a tree object. */
|
|
21
|
+
const ATTRIBUTE_PREFIX = "@_";
|
|
22
|
+
|
|
23
|
+
/** The key of an element's character data when it also has attributes or children. */
|
|
24
|
+
const TEXT_KEY = "#text";
|
|
25
|
+
|
|
26
|
+
/** A tree object: attributes, text and child elements by name. */
|
|
27
|
+
type XmlTreeObject = Record<string, unknown>;
|
|
28
|
+
|
|
29
|
+
/** A frame of the builder: one open element. */
|
|
30
|
+
interface Frame {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly node: XmlTreeObject;
|
|
33
|
+
hasAttrs: boolean;
|
|
34
|
+
hasChildren: boolean;
|
|
35
|
+
text: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds the tree of one subtree from start / end / text events. The outermost frame is the
|
|
40
|
+
* `<data>` element itself; `finish()` returns its content: the object of its children, or its
|
|
41
|
+
* text when it has none.
|
|
42
|
+
*/
|
|
43
|
+
export class XmlTreeBuilder {
|
|
44
|
+
private readonly frames: Frame[] = [];
|
|
45
|
+
|
|
46
|
+
private readonly root: Frame;
|
|
47
|
+
|
|
48
|
+
/** Create a builder whose implicit root is the containing element. */
|
|
49
|
+
constructor() {
|
|
50
|
+
this.root = { name: "", node: {}, hasAttrs: false, hasChildren: false, text: "" };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether the builder is inside a child element (as opposed to at the root level).
|
|
55
|
+
* @returns true while at least one child element is open
|
|
56
|
+
*/
|
|
57
|
+
get open(): boolean {
|
|
58
|
+
return this.frames.length > 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* An element starts.
|
|
63
|
+
* @param name - the element name as written
|
|
64
|
+
* @param attrs - its attributes
|
|
65
|
+
*/
|
|
66
|
+
start(name: string, attrs: ReadonlyMap<string, string>): void {
|
|
67
|
+
const node: XmlTreeObject = {};
|
|
68
|
+
let hasAttrs = false;
|
|
69
|
+
for (const [key, value] of attrs) {
|
|
70
|
+
node[ATTRIBUTE_PREFIX + key] = value;
|
|
71
|
+
hasAttrs = true;
|
|
72
|
+
}
|
|
73
|
+
this.frames.push({ name, node, hasAttrs, hasChildren: false, text: "" });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Character data inside the current element.
|
|
78
|
+
* @param text - the text
|
|
79
|
+
*/
|
|
80
|
+
text(text: string): void {
|
|
81
|
+
const frame = this.frames.length > 0 ? this.frames[this.frames.length - 1] : this.root;
|
|
82
|
+
frame.text += text;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The current element ends; its value is attached to its parent. */
|
|
86
|
+
end(): void {
|
|
87
|
+
const frame = this.frames.pop();
|
|
88
|
+
if (frame === undefined) {
|
|
89
|
+
throw new GraphFormatError("E_COLUMN_TYPE", "XmlTreeBuilder.end() without a matching start()", {
|
|
90
|
+
reason: "tree builder underflow",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const parent = this.frames.length > 0 ? this.frames[this.frames.length - 1] : this.root;
|
|
94
|
+
attachChild(parent, frame.name, valueOf(frame));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The content of the containing element: its text when it has no child elements, otherwise
|
|
99
|
+
* the object of its children (with `#text` for non-whitespace mixed content).
|
|
100
|
+
* @returns the value to store in the json column
|
|
101
|
+
*/
|
|
102
|
+
finish(): unknown {
|
|
103
|
+
if (this.frames.length > 0) {
|
|
104
|
+
throw new GraphFormatError("E_COLUMN_TYPE", "XmlTreeBuilder.finish() with open elements", {
|
|
105
|
+
reason: "tree builder open",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const { root } = this;
|
|
109
|
+
if (!root.hasChildren) {
|
|
110
|
+
return root.text;
|
|
111
|
+
}
|
|
112
|
+
if (!isWhitespace(root.text)) {
|
|
113
|
+
root.node[TEXT_KEY] = root.text;
|
|
114
|
+
}
|
|
115
|
+
return root.node;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The value of a finished element: its text when it has neither attributes nor children,
|
|
121
|
+
* else its node with `#text` set to non-whitespace text.
|
|
122
|
+
* @param frame - the finished frame
|
|
123
|
+
* @returns the value
|
|
124
|
+
*/
|
|
125
|
+
function valueOf(frame: Frame): unknown {
|
|
126
|
+
if (!frame.hasAttrs && !frame.hasChildren) {
|
|
127
|
+
return frame.text;
|
|
128
|
+
}
|
|
129
|
+
if (!isWhitespace(frame.text)) {
|
|
130
|
+
frame.node[TEXT_KEY] = frame.text;
|
|
131
|
+
}
|
|
132
|
+
return frame.node;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Attach a child value under a name, turning a repeated name into an array.
|
|
137
|
+
* @param parent - the parent frame
|
|
138
|
+
* @param name - the child element name
|
|
139
|
+
* @param value - the child value
|
|
140
|
+
*/
|
|
141
|
+
function attachChild(parent: Frame, name: string, value: unknown): void {
|
|
142
|
+
parent.hasChildren = true;
|
|
143
|
+
const existing = parent.node[name];
|
|
144
|
+
if (existing === undefined) {
|
|
145
|
+
parent.node[name] = value;
|
|
146
|
+
} else if (Array.isArray(existing)) {
|
|
147
|
+
existing.push(value);
|
|
148
|
+
} else {
|
|
149
|
+
parent.node[name] = [existing, value];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Whether a value is a tree the writer can serialise: a string, number, boolean or null (text),
|
|
155
|
+
* an array of such trees, or an object whose keys are `#text`, `@_<Name>` or `<Name>` with tree
|
|
156
|
+
* values.
|
|
157
|
+
* @param value - the value
|
|
158
|
+
* @returns true when writeXmlTree() accepts it
|
|
159
|
+
*/
|
|
160
|
+
export function isXmlTree(value: unknown): boolean {
|
|
161
|
+
return treeProblem(value) === null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Why a value is not a serialisable tree.
|
|
166
|
+
* @param value - the value
|
|
167
|
+
* @returns a message, or null when the value is a tree
|
|
168
|
+
*/
|
|
169
|
+
export function treeProblem(value: unknown): string | null {
|
|
170
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
if (Array.isArray(value)) {
|
|
174
|
+
for (const item of value) {
|
|
175
|
+
if (Array.isArray(item)) {
|
|
176
|
+
return "nested arrays cannot be written as elements";
|
|
177
|
+
}
|
|
178
|
+
const problem = treeProblem(item);
|
|
179
|
+
if (problem !== null) {
|
|
180
|
+
return problem;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
if (typeof value !== "object") {
|
|
186
|
+
return `a ${typeof value} cannot be written as XML`;
|
|
187
|
+
}
|
|
188
|
+
for (const [key, child] of Object.entries(value)) {
|
|
189
|
+
if (key === TEXT_KEY) {
|
|
190
|
+
if (child !== null && typeof child === "object") {
|
|
191
|
+
return "#text must be a scalar";
|
|
192
|
+
}
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (key.startsWith(ATTRIBUTE_PREFIX)) {
|
|
196
|
+
const name = key.slice(ATTRIBUTE_PREFIX.length);
|
|
197
|
+
if (!isXmlName(name)) {
|
|
198
|
+
return `"${name}" is not an XML attribute name`;
|
|
199
|
+
}
|
|
200
|
+
if (child !== null && typeof child === "object") {
|
|
201
|
+
return `attribute ${name} must be a scalar`;
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (!isXmlName(key)) {
|
|
206
|
+
return `"${key}" is not an XML element name`;
|
|
207
|
+
}
|
|
208
|
+
const problem = treeProblem(child);
|
|
209
|
+
if (problem !== null) {
|
|
210
|
+
return problem;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Write the content of a tree as XML text: the children (and mixed text) of the containing
|
|
218
|
+
* element. Leaf elements are written inline so their text stays exact; elements with children
|
|
219
|
+
* are broken over lines and indented.
|
|
220
|
+
* @param value - the tree (the content of a `<data>` element)
|
|
221
|
+
* @param indent - the indentation of the containing element's children
|
|
222
|
+
* @param out - receives the text parts
|
|
223
|
+
*/
|
|
224
|
+
export function writeXmlTree(value: unknown, indent: string, out: string[]): void {
|
|
225
|
+
const problem = treeProblem(value);
|
|
226
|
+
if (problem !== null) {
|
|
227
|
+
throw new GraphFormatError("E_COLUMN_TYPE", `a yfiles value cannot be written as XML: ${problem}`, {
|
|
228
|
+
reason: "xml tree",
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
232
|
+
out.push(escapeXmlText(scalarText(value)));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const object = value as XmlTreeObject;
|
|
236
|
+
let text: string | null = null;
|
|
237
|
+
for (const [key, child] of Object.entries(object)) {
|
|
238
|
+
if (key === TEXT_KEY) {
|
|
239
|
+
text = scalarText(child);
|
|
240
|
+
} else if (!key.startsWith(ATTRIBUTE_PREFIX)) {
|
|
241
|
+
writeElements(key, child, indent, out);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (text !== null) {
|
|
245
|
+
out.push(escapeXmlText(text));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Write one element (or, for an array, one element per item).
|
|
251
|
+
* @param name - the element name
|
|
252
|
+
* @param value - the element value
|
|
253
|
+
* @param indent - the indentation of this element
|
|
254
|
+
* @param out - receives the text parts
|
|
255
|
+
*/
|
|
256
|
+
function writeElements(name: string, value: unknown, indent: string, out: string[]): void {
|
|
257
|
+
if (Array.isArray(value)) {
|
|
258
|
+
for (const item of value) {
|
|
259
|
+
writeElements(name, item, indent, out);
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (value === null || typeof value !== "object") {
|
|
264
|
+
const text = scalarText(value);
|
|
265
|
+
out.push(text.length === 0 ? `\n${indent}<${name}/>` : `\n${indent}<${name}>${escapeXmlText(text)}</${name}>`);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const object = value as XmlTreeObject;
|
|
269
|
+
let attrs = "";
|
|
270
|
+
let text: string | null = null;
|
|
271
|
+
let hasChildren = false;
|
|
272
|
+
for (const [key, child] of Object.entries(object)) {
|
|
273
|
+
if (key === TEXT_KEY) {
|
|
274
|
+
text = scalarText(child);
|
|
275
|
+
} else if (key.startsWith(ATTRIBUTE_PREFIX)) {
|
|
276
|
+
attrs += ` ${key.slice(ATTRIBUTE_PREFIX.length)}="${escapeXmlAttribute(scalarText(child))}"`;
|
|
277
|
+
} else {
|
|
278
|
+
hasChildren = true;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (!hasChildren) {
|
|
282
|
+
out.push(
|
|
283
|
+
text === null || text.length === 0
|
|
284
|
+
? `\n${indent}<${name}${attrs}/>`
|
|
285
|
+
: `\n${indent}<${name}${attrs}>${escapeXmlText(text)}</${name}>`,
|
|
286
|
+
);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
out.push(`\n${indent}<${name}${attrs}>`);
|
|
290
|
+
const inner = `${indent} `;
|
|
291
|
+
for (const [key, child] of Object.entries(object)) {
|
|
292
|
+
if (key !== TEXT_KEY && !key.startsWith(ATTRIBUTE_PREFIX)) {
|
|
293
|
+
writeElements(key, child, inner, out);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (text !== null) {
|
|
297
|
+
out.push(escapeXmlText(text));
|
|
298
|
+
}
|
|
299
|
+
out.push(`\n${indent}</${name}>`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* The text of a scalar tree value.
|
|
304
|
+
* @param value - a string, number, boolean or null
|
|
305
|
+
* @returns the text; an empty string for null
|
|
306
|
+
*/
|
|
307
|
+
function scalarText(value: unknown): string {
|
|
308
|
+
switch (typeof value) {
|
|
309
|
+
case "string":
|
|
310
|
+
return value;
|
|
311
|
+
case "number":
|
|
312
|
+
case "boolean":
|
|
313
|
+
case "bigint":
|
|
314
|
+
return String(value);
|
|
315
|
+
default:
|
|
316
|
+
return "";
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the JSON importer and exporter share (design sections 8.2 and 8.5): the dialect names, the
|
|
3
|
+
* reserved `meta.extra.json` shape record the importer writes and the exporter reads back, the
|
|
4
|
+
* per-dialect capability tables, the fixed key sets of each dialect and the small JSON value
|
|
5
|
+
* helpers both sides use.
|
|
6
|
+
*
|
|
7
|
+
* Dialects: NetworkX node-link (`nodes` + `links` / `edges`, graph-level `directed` / `multigraph`
|
|
8
|
+
* / `graph`), the d3 lineage of the same shape (`links`, `name` ids, integer index endpoints),
|
|
9
|
+
* JSON Graph Format v2 (`graph.nodes` keyed by id, per-edge `directed`, hyperedges), Cytoscape.js
|
|
10
|
+
* elements (`data.id` / `data.source` / `data.target`, `position`, `classes`, `data.parent`),
|
|
11
|
+
* graphology serialisation (`key` / `attributes`, `undirected` edges, `options.type` / `multi`) and
|
|
12
|
+
* vis.js (`from` / `to`).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { type GraphMeta } from "@graphty/graph-format";
|
|
16
|
+
|
|
17
|
+
import { capabilities } from "../../common/export.js";
|
|
18
|
+
import { type ExportCapabilities } from "../../types.js";
|
|
19
|
+
|
|
20
|
+
/** The JSON dialects the plugin reads and writes. */
|
|
21
|
+
export type JsonDialect = "node-link" | "d3" | "jgf" | "cytoscape" | "graphology" | "vis";
|
|
22
|
+
|
|
23
|
+
/** Every dialect name, for option checking and messages. */
|
|
24
|
+
export const JSON_DIALECTS: readonly JsonDialect[] = Object.freeze([
|
|
25
|
+
"node-link",
|
|
26
|
+
"d3",
|
|
27
|
+
"jgf",
|
|
28
|
+
"cytoscape",
|
|
29
|
+
"graphology",
|
|
30
|
+
"vis",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
/** The key under `meta.extra` that holds the shape record (design section 8.5). */
|
|
34
|
+
export const META_KEY = "json";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The shape information the importer records under `meta.extra.json` so the exporter can write the
|
|
38
|
+
* same file back (design section 8.5). Every field is optional because a snapshot may come from
|
|
39
|
+
* another format or from an older record.
|
|
40
|
+
*/
|
|
41
|
+
export interface JsonShapeMeta {
|
|
42
|
+
/** The dialect the file was read as. */
|
|
43
|
+
readonly dialect?: JsonDialect | undefined;
|
|
44
|
+
/** node-link / d3: the key the edge array was under ("links" or "edges"). */
|
|
45
|
+
readonly edgesKey?: string | undefined;
|
|
46
|
+
/** node-link / d3: the key holding the node id ("id", "name", ...); null when nodes were positional. */
|
|
47
|
+
readonly nodeIdKey?: string | null | undefined;
|
|
48
|
+
/** node-link / d3: whether edge endpoints were array positions rather than ids. */
|
|
49
|
+
readonly indexLinks?: boolean | undefined;
|
|
50
|
+
/** node-link / d3 / vis: the endpoint keys the file used. */
|
|
51
|
+
readonly sourceKey?: string | undefined;
|
|
52
|
+
/** node-link / d3 / vis: the endpoint keys the file used. */
|
|
53
|
+
readonly targetKey?: string | undefined;
|
|
54
|
+
/** jgf: the graph `id`. */
|
|
55
|
+
readonly id?: string | undefined;
|
|
56
|
+
/** jgf: the graph `type`. */
|
|
57
|
+
readonly type?: string | undefined;
|
|
58
|
+
/** graphology: `options.allowSelfLoops` as declared. */
|
|
59
|
+
readonly allowSelfLoops?: boolean | undefined;
|
|
60
|
+
/** cytoscape: every top-level key besides `elements` and `data` (style, zoom, pan, ...), verbatim. */
|
|
61
|
+
readonly cytoscape?: Readonly<Record<string, unknown>> | undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Whether a value is a plain JSON object (not null, not an array).
|
|
66
|
+
* @param value - any value
|
|
67
|
+
* @returns true for an object that is not an array
|
|
68
|
+
*/
|
|
69
|
+
export function isJsonObject(value: unknown): value is Record<string, unknown> {
|
|
70
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Whether a record has an own property; JSON.parse output is checked with hasOwnProperty so a key
|
|
75
|
+
* named "constructor" or "__proto__" is never read from the prototype.
|
|
76
|
+
* @param record - the record
|
|
77
|
+
* @param key - the key
|
|
78
|
+
* @returns true when the key is an own property
|
|
79
|
+
*/
|
|
80
|
+
export function hasKey(record: Readonly<Record<string, unknown>>, key: string): boolean {
|
|
81
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Whether a text names a dialect.
|
|
86
|
+
* @param value - any value
|
|
87
|
+
* @returns true for one of JSON_DIALECTS
|
|
88
|
+
*/
|
|
89
|
+
export function isJsonDialect(value: unknown): value is JsonDialect {
|
|
90
|
+
return typeof value === "string" && (JSON_DIALECTS as readonly string[]).includes(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The dialect of a parsed JSON document, by the shape rules of design section 8.2 (Cytoscape:
|
|
95
|
+
* `elements` or a top-level array of `{ data }` elements; JGF: `graph.nodes` / `graph.edges` or
|
|
96
|
+
* `graphs[]`; graphology: `options.type` / `options.multi`, `key` nodes without `id`, edges with
|
|
97
|
+
* `undirected` or an `attributes` record; vis: edges with `from` / `to`; d3: `links` without
|
|
98
|
+
* `directed` / `multigraph` / `graph`; else node-link). Pure: the importer wraps it with its issue
|
|
99
|
+
* codes, the registry's sniff() uses it on a head that parses as a whole document.
|
|
100
|
+
* @param root - the parsed document
|
|
101
|
+
* @returns the dialect, or null when the document is not a graph document in any dialect
|
|
102
|
+
*/
|
|
103
|
+
export function sniffJsonDialect(root: unknown): JsonDialect | null {
|
|
104
|
+
if (Array.isArray(root)) {
|
|
105
|
+
const first = firstJsonObject(root);
|
|
106
|
+
return root.length === 0 || (first !== null && isJsonObject(first.data)) ? "cytoscape" : null;
|
|
107
|
+
}
|
|
108
|
+
if (!isJsonObject(root)) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (hasKey(root, "elements")) {
|
|
112
|
+
return "cytoscape";
|
|
113
|
+
}
|
|
114
|
+
if (isJsonObject(root.graph) && (hasKey(root.graph, "nodes") || hasKey(root.graph, "edges"))) {
|
|
115
|
+
return "jgf";
|
|
116
|
+
}
|
|
117
|
+
if (Array.isArray(root.graphs)) {
|
|
118
|
+
return "jgf";
|
|
119
|
+
}
|
|
120
|
+
if (!hasKey(root, "nodes") && !hasKey(root, "edges") && !hasKey(root, "links")) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const firstNode = firstJsonObject(root.nodes);
|
|
124
|
+
const firstEdge = firstJsonObject(hasKey(root, "edges") ? root.edges : root.links);
|
|
125
|
+
if (isJsonObject(root.options) && (hasKey(root.options, "type") || hasKey(root.options, "multi"))) {
|
|
126
|
+
return "graphology";
|
|
127
|
+
}
|
|
128
|
+
if (firstNode !== null && hasKey(firstNode, "key") && !hasKey(firstNode, "id")) {
|
|
129
|
+
return "graphology";
|
|
130
|
+
}
|
|
131
|
+
if (firstEdge !== null && !hasKey(firstEdge, "from")) {
|
|
132
|
+
if (hasKey(firstEdge, "undirected") || isJsonObject(firstEdge.attributes)) {
|
|
133
|
+
return "graphology";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (firstEdge !== null && hasKey(firstEdge, "from") && hasKey(firstEdge, "to") && !hasKey(firstEdge, "source")) {
|
|
137
|
+
return "vis";
|
|
138
|
+
}
|
|
139
|
+
const bare = !hasKey(root, "directed") && !hasKey(root, "multigraph") && !hasKey(root, "graph");
|
|
140
|
+
return bare && hasKey(root, "links") ? "d3" : "node-link";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The first object of an array, or null.
|
|
145
|
+
* @param value - maybe an array
|
|
146
|
+
* @returns the first element that is an object
|
|
147
|
+
*/
|
|
148
|
+
function firstJsonObject(value: unknown): Record<string, unknown> | null {
|
|
149
|
+
if (!Array.isArray(value)) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
const found: unknown = value.find((item) => isJsonObject(item));
|
|
153
|
+
return isJsonObject(found) ? found : null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The shape record of a snapshot's metadata, or an empty record when the snapshot did not come
|
|
158
|
+
* from the JSON importer.
|
|
159
|
+
* @param meta - the snapshot's metadata
|
|
160
|
+
* @returns the fields found under `meta.extra.json`, each only when it has the expected type
|
|
161
|
+
*/
|
|
162
|
+
export function shapeMetaOf(meta: GraphMeta): JsonShapeMeta {
|
|
163
|
+
const raw: unknown = meta.extra[META_KEY];
|
|
164
|
+
if (!isJsonObject(raw)) {
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
const text = (key: string): string | undefined => (typeof raw[key] === "string" ? raw[key] : undefined);
|
|
168
|
+
const flag = (key: string): boolean | undefined => (typeof raw[key] === "boolean" ? raw[key] : undefined);
|
|
169
|
+
const { nodeIdKey } = raw;
|
|
170
|
+
return {
|
|
171
|
+
dialect: isJsonDialect(raw.dialect) ? raw.dialect : undefined,
|
|
172
|
+
edgesKey: text("edgesKey"),
|
|
173
|
+
nodeIdKey: nodeIdKey === null || typeof nodeIdKey === "string" ? nodeIdKey : undefined,
|
|
174
|
+
indexLinks: flag("indexLinks"),
|
|
175
|
+
sourceKey: text("sourceKey"),
|
|
176
|
+
targetKey: text("targetKey"),
|
|
177
|
+
id: text("id"),
|
|
178
|
+
type: text("type"),
|
|
179
|
+
allowSelfLoops: flag("allowSelfLoops"),
|
|
180
|
+
cytoscape: isJsonObject(raw.cytoscape) ? raw.cytoscape : undefined,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The direction a dialect assumes when the file declares none (design section 8.4, `defaultDirected`). */
|
|
185
|
+
export const DIALECT_DEFAULT_DIRECTED: Readonly<Record<JsonDialect, boolean>> = Object.freeze({
|
|
186
|
+
"node-link": false,
|
|
187
|
+
d3: false,
|
|
188
|
+
jgf: true,
|
|
189
|
+
cytoscape: true,
|
|
190
|
+
graphology: true,
|
|
191
|
+
vis: false,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The dtypes a JSON dialect keeps as declared: JSON declares no types, so a value reads back as
|
|
196
|
+
* what the importer's inference gives it (design section 5.1: integral numbers i32, other numbers
|
|
197
|
+
* f64, booleans, strings). f32 values re-read as f64 differ from the f32 ones, u32 / u8 come back
|
|
198
|
+
* i32, dict comes back string, a multi-component or list value comes back json.
|
|
199
|
+
*/
|
|
200
|
+
const JSON_DTYPES = Object.freeze(["f64", "i32", "bool", "string"] as const);
|
|
201
|
+
|
|
202
|
+
const COMMON = {
|
|
203
|
+
multiEdges: true,
|
|
204
|
+
selfLoops: true,
|
|
205
|
+
idCharset: "any",
|
|
206
|
+
dtypes: JSON_DTYPES,
|
|
207
|
+
components: false,
|
|
208
|
+
lists: false,
|
|
209
|
+
json: true,
|
|
210
|
+
defaults: false,
|
|
211
|
+
options: false,
|
|
212
|
+
temporal: "none",
|
|
213
|
+
positions: false,
|
|
214
|
+
viz: false,
|
|
215
|
+
} as const;
|
|
216
|
+
|
|
217
|
+
const TABLES: Readonly<Record<JsonDialect, ExportCapabilities>> = Object.freeze({
|
|
218
|
+
"node-link": capabilities({
|
|
219
|
+
...COMMON,
|
|
220
|
+
mixedDirection: false,
|
|
221
|
+
edgeIds: "none",
|
|
222
|
+
hierarchy: false,
|
|
223
|
+
graphAttributes: true,
|
|
224
|
+
}),
|
|
225
|
+
d3: capabilities({
|
|
226
|
+
...COMMON,
|
|
227
|
+
mixedDirection: false,
|
|
228
|
+
edgeIds: "none",
|
|
229
|
+
hierarchy: false,
|
|
230
|
+
graphAttributes: false,
|
|
231
|
+
}),
|
|
232
|
+
jgf: capabilities({
|
|
233
|
+
...COMMON,
|
|
234
|
+
mixedDirection: true,
|
|
235
|
+
edgeIds: "optional",
|
|
236
|
+
hierarchy: false,
|
|
237
|
+
graphAttributes: true,
|
|
238
|
+
}),
|
|
239
|
+
cytoscape: capabilities({
|
|
240
|
+
...COMMON,
|
|
241
|
+
positions: true,
|
|
242
|
+
mixedDirection: false,
|
|
243
|
+
edgeIds: "required",
|
|
244
|
+
hierarchy: true,
|
|
245
|
+
graphAttributes: true,
|
|
246
|
+
}),
|
|
247
|
+
graphology: capabilities({
|
|
248
|
+
...COMMON,
|
|
249
|
+
mixedDirection: true,
|
|
250
|
+
edgeIds: "optional",
|
|
251
|
+
hierarchy: false,
|
|
252
|
+
graphAttributes: true,
|
|
253
|
+
}),
|
|
254
|
+
vis: capabilities({
|
|
255
|
+
...COMMON,
|
|
256
|
+
mixedDirection: false,
|
|
257
|
+
edgeIds: "optional",
|
|
258
|
+
hierarchy: false,
|
|
259
|
+
graphAttributes: false,
|
|
260
|
+
}),
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* What one dialect can express (design section 8.5): positions and visual columns are written as
|
|
265
|
+
* plain attributes (an array for a multi-component column) and read back without their role,
|
|
266
|
+
* except Cytoscape's `position` object; containment only as the Cytoscape `data.parent`; mixed
|
|
267
|
+
* direction only where the dialect carries a per-edge flag (JGF `directed`, graphology
|
|
268
|
+
* `undirected`); edge ids where the dialect has a slot (not node-link / d3); nothing temporal;
|
|
269
|
+
* graph attributes everywhere but d3 (the bare shape) and vis.
|
|
270
|
+
* @param dialect - the dialect
|
|
271
|
+
* @returns its frozen capability table
|
|
272
|
+
*/
|
|
273
|
+
export function dialectCapabilities(dialect: JsonDialect): ExportCapabilities {
|
|
274
|
+
return TABLES[dialect];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** The Cytoscape element-level keys (everything else on an element lives under `data`). */
|
|
278
|
+
export const CYTOSCAPE_ELEMENT_KEYS: ReadonlySet<string> = new Set([
|
|
279
|
+
"selected",
|
|
280
|
+
"selectable",
|
|
281
|
+
"locked",
|
|
282
|
+
"grabbable",
|
|
283
|
+
"pannable",
|
|
284
|
+
"removed",
|
|
285
|
+
"scratch",
|
|
286
|
+
"renderedPosition",
|
|
287
|
+
]);
|
|
288
|
+
|
|
289
|
+
/** The Cytoscape element keys the importer maps structurally rather than to columns. */
|
|
290
|
+
export const CYTOSCAPE_STRUCTURAL_KEYS: ReadonlySet<string> = new Set(["data", "group", "position", "classes"]);
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* The suffix the importer appends to a column name that collides with a structural column or a
|
|
294
|
+
* reserved key of its dialect (design section 5.6 names collisions deterministically); the exporter
|
|
295
|
+
* strips it when the value goes back to the level the suffix names.
|
|
296
|
+
*/
|
|
297
|
+
export const SUFFIX = Object.freeze({
|
|
298
|
+
/** A Cytoscape `data` key or a JGF / graphology attribute that collides with a structural column. */
|
|
299
|
+
data: "#data",
|
|
300
|
+
/** An element-level key the dialect does not define (Cytoscape, JGF, graphology). */
|
|
301
|
+
element: "#element",
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
/** The default source keys of a node-link edge record, tried in order (as fromRecords does). */
|
|
305
|
+
export const NODE_LINK_SOURCE_KEYS: readonly string[] = Object.freeze(["source", "src", "from"]);
|
|
306
|
+
|
|
307
|
+
/** The default target keys of a node-link edge record, tried in order. */
|
|
308
|
+
export const NODE_LINK_TARGET_KEYS: readonly string[] = Object.freeze(["target", "dst", "to"]);
|
|
309
|
+
|
|
310
|
+
/** The name of the position column every JSON dialect writes / reads (design section 5.2). */
|
|
311
|
+
export const POSITION_COLUMN = "position";
|
|
312
|
+
|
|
313
|
+
/** The name of the Cytoscape classes column (a list of strings with role "classes"). */
|
|
314
|
+
export const CLASSES_COLUMN = "classes";
|
|
315
|
+
|
|
316
|
+
/** The name of the Cytoscape parent column (u32 refersTo node, role "parent"). */
|
|
317
|
+
export const PARENT_COLUMN = "parent";
|