@graphty/graph-io 0.3.3 → 0.3.4
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 +17 -4
- package/dist/chunks/{importer-BELrICWM.js → importer-BOxwmef_.js} +352 -19
- package/dist/chunks/importer-BOxwmef_.js.map +1 -0
- package/dist/graph-io.js +2 -2
- package/dist/graph-io.js.map +1 -1
- package/dist/json.js +1 -1
- package/dist/src/formats/json/dialect.d.ts +21 -4
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +29 -4
- package/dist/src/formats/json/dialect.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +27 -7
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts +17 -4
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +356 -8
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/index.d.ts +1 -1
- package/dist/src/formats/json/index.d.ts.map +1 -1
- package/dist/src/formats/json/index.js +1 -1
- package/dist/src/formats/json/index.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/sniff.d.ts +3 -3
- package/dist/src/sniff.d.ts.map +1 -1
- package/dist/src/sniff.js.map +1 -1
- package/package.json +1 -1
- package/src/common/escape.ts +5 -4
- package/src/formats/json/dialect.ts +40 -6
- package/src/formats/json/exporter.ts +28 -7
- package/src/formats/json/importer.ts +411 -21
- package/src/formats/json/index.ts +7 -1
- package/src/formats/neo4j/exporter.ts +43 -6
- package/src/formats/neo4j/importer.ts +56 -11
- package/src/formats/neo4j/index.ts +10 -3
- package/src/formats/pajek/exporter.ts +3 -1
- package/src/formats/pajek/importer.ts +284 -60
- package/src/formats/pajek/syntax.ts +98 -12
- package/src/index.ts +2 -0
- package/src/sniff.ts +3 -3
- package/dist/chunks/importer-BELrICWM.js.map +0 -1
|
@@ -19,6 +19,14 @@
|
|
|
19
19
|
* `idSpace`), and a stored id `name:ID(Space)` also a string node column `name` whose
|
|
20
20
|
* `origin.namespace` is the space. A property whose name collides with one of those is renamed
|
|
21
21
|
* `<name>#<name>` (design section 5.6).
|
|
22
|
+
* - Id spaces: neo4j-admin keeps one id space per `:ID(Space)` name, so `1` in `:ID(Product)` and
|
|
23
|
+
* `1` in `:ID(Category)` are two nodes. The core has one id space, so a node of a spaced section
|
|
24
|
+
* is stored under the string id `Space:id` (every row of the section, collision or not, so an id
|
|
25
|
+
* never depends on file order), its id text goes into the node string column `originalId` and
|
|
26
|
+
* its space into `idSpace`. The column has no role: the `originalId` role means an id rewritten by
|
|
27
|
+
* `sanitizeIds: "mangle"`, which other importers restore as the node id. `:START_ID(Space)` /
|
|
28
|
+
* `:END_ID(Space)` endpoints are qualified the same way before the lookup; an endpoint without a
|
|
29
|
+
* space is looked up as written.
|
|
22
30
|
* - Ids are text cells coerced by the `ids` option ("canonical" by default, so `1` is the number 1
|
|
23
31
|
* and `007` stays a string; integers beyond 2^53 stay strings). Relationships are always directed
|
|
24
32
|
* ("In Neo4j, all relationships have a direction"), so the sink is set directed before the first
|
|
@@ -104,6 +112,9 @@ export const TYPE_COLUMN = "type";
|
|
|
104
112
|
/** The name of the node dict column holding the id space of `:ID(Space)`. */
|
|
105
113
|
export const ID_SPACE_COLUMN = "idSpace";
|
|
106
114
|
|
|
115
|
+
/** The name of the node string column holding the id text of a node of an id space (its id is `Space:id`). */
|
|
116
|
+
export const ORIGINAL_ID_COLUMN = "originalId";
|
|
117
|
+
|
|
107
118
|
/** Issue code: a header row (or a whole section) is malformed; the import aborts. */
|
|
108
119
|
export const HEADER_CODE = "E_NEO4J_HEADER";
|
|
109
120
|
|
|
@@ -119,12 +130,15 @@ export const MISSING_ENDPOINT_CODE = SHARED_MISSING_ENDPOINT_CODE;
|
|
|
119
130
|
/** Issue code: a node id was declared twice (same id space); the later row's properties win. */
|
|
120
131
|
export const DUPLICATE_NODE_CODE = SHARED_DUPLICATE_NODE_CODE;
|
|
121
132
|
|
|
122
|
-
/**
|
|
133
|
+
/**
|
|
134
|
+
* Issue code: a node id was declared in two id spaces -- a spaced id `Space:id` equals the text of an
|
|
135
|
+
* id declared without a space; the later row is skipped.
|
|
136
|
+
*/
|
|
123
137
|
export const ID_SPACE_COLLISION_CODE = "E_NEO4J_ID_SPACE_COLLISION";
|
|
124
138
|
|
|
125
139
|
/**
|
|
126
|
-
* Issue code: a `:START_ID(Space)` / `:END_ID(Space)` id names a node a
|
|
127
|
-
*
|
|
140
|
+
* Issue code: a `:START_ID(Space)` / `:END_ID(Space)` endpoint's qualified id `Space:id` names a node a
|
|
141
|
+
* node row declared in another id space (without a space); the row is skipped.
|
|
128
142
|
*/
|
|
129
143
|
export const ENDPOINT_SPACE_CODE = "E_NEO4J_ENDPOINT_SPACE";
|
|
130
144
|
|
|
@@ -185,6 +199,14 @@ const ID_SPACE_DECL: ColumnDecl = {
|
|
|
185
199
|
origin: { format: NEO4J, id: ":ID", title: null, type: "ID", namespace: null },
|
|
186
200
|
};
|
|
187
201
|
|
|
202
|
+
const ORIGINAL_ID_DECL: ColumnDecl = {
|
|
203
|
+
name: ORIGINAL_ID_COLUMN,
|
|
204
|
+
dtype: "string",
|
|
205
|
+
nullable: true,
|
|
206
|
+
// origin.type stays null so the column is never mistaken for a stored id (`name:ID`)
|
|
207
|
+
origin: { format: NEO4J, id: ":ID", title: null, type: null, namespace: null },
|
|
208
|
+
};
|
|
209
|
+
|
|
188
210
|
const ARRAY_DELIMITERS: Readonly<Record<string, ListSyntax>> = { ";": "semicolon", ",": "comma", "|": "pipe" };
|
|
189
211
|
|
|
190
212
|
/** The resolved format-specific options. */
|
|
@@ -231,6 +253,9 @@ interface RelationshipSection {
|
|
|
231
253
|
readonly width: number;
|
|
232
254
|
readonly startCell: number;
|
|
233
255
|
readonly endCell: number;
|
|
256
|
+
/** The id space of `:START_ID` / `:END_ID`, or null when the header declares none. */
|
|
257
|
+
readonly startSpace: string | null;
|
|
258
|
+
readonly endSpace: string | null;
|
|
234
259
|
/** The id space of `:START_ID` / `:END_ID` as a registry code, or 0 when the header declares none. */
|
|
235
260
|
readonly startSpaceCode: number;
|
|
236
261
|
readonly endSpaceCode: number;
|
|
@@ -443,6 +468,8 @@ class Neo4jImportSession {
|
|
|
443
468
|
|
|
444
469
|
private idSpaceHandle: ColumnHandle = INVALID_INDEX as ColumnHandle;
|
|
445
470
|
|
|
471
|
+
private originalIdHandle: ColumnHandle = INVALID_INDEX as ColumnHandle;
|
|
472
|
+
|
|
446
473
|
private ignoredColumns = 0;
|
|
447
474
|
|
|
448
475
|
/** Whether the sink's direction was set (before the first relationship, design section 8.4 rule 1). */
|
|
@@ -700,6 +727,8 @@ class Neo4jImportSession {
|
|
|
700
727
|
width: fields.length,
|
|
701
728
|
startCell,
|
|
702
729
|
endCell,
|
|
730
|
+
startSpace,
|
|
731
|
+
endSpace,
|
|
703
732
|
startSpaceCode: startSpace === null ? 0 : this.registry.codeOf(startSpace),
|
|
704
733
|
endSpaceCode: endSpace === null ? 0 : this.registry.codeOf(endSpace),
|
|
705
734
|
typeCell,
|
|
@@ -809,6 +838,7 @@ class Neo4jImportSession {
|
|
|
809
838
|
private ensureIdSpace(): void {
|
|
810
839
|
if (this.idSpaceHandle === INVALID_INDEX) {
|
|
811
840
|
this.idSpaceHandle = this.declareReserved("node", ID_SPACE_DECL);
|
|
841
|
+
this.originalIdHandle = this.declareReserved("node", ORIGINAL_ID_DECL);
|
|
812
842
|
}
|
|
813
843
|
}
|
|
814
844
|
|
|
@@ -881,11 +911,12 @@ class Neo4jImportSession {
|
|
|
881
911
|
report.counts.skippedNodes++;
|
|
882
912
|
return;
|
|
883
913
|
}
|
|
884
|
-
const
|
|
885
|
-
if (
|
|
914
|
+
const coerced = this.coerceId(idText, line);
|
|
915
|
+
if (coerced === null || !this.parseProperties(section.properties, cells, quoted, line, idText)) {
|
|
886
916
|
report.counts.skippedNodes++;
|
|
887
917
|
return;
|
|
888
918
|
}
|
|
919
|
+
const id = qualify(coerced, section.space);
|
|
889
920
|
let labels: string[] | undefined;
|
|
890
921
|
if (section.labelCells.length > 0 || section.extraLabels.length > 0) {
|
|
891
922
|
labels = this.labelsOf(section, cells, quoted);
|
|
@@ -918,6 +949,7 @@ class Neo4jImportSession {
|
|
|
918
949
|
}
|
|
919
950
|
if (section.space !== null) {
|
|
920
951
|
sink.setNodeValue(this.idSpaceHandle, index, section.space);
|
|
952
|
+
sink.setNodeValue(this.originalIdHandle, index, idText);
|
|
921
953
|
}
|
|
922
954
|
if (labels !== undefined) {
|
|
923
955
|
sink.setNodeValue(this.labelsHandle, index, labels);
|
|
@@ -968,12 +1000,14 @@ class Neo4jImportSession {
|
|
|
968
1000
|
return;
|
|
969
1001
|
}
|
|
970
1002
|
const element = `${startText}->${endText}`;
|
|
971
|
-
const
|
|
972
|
-
const
|
|
973
|
-
if (
|
|
1003
|
+
const start = this.coerceId(startText, line);
|
|
1004
|
+
const end = start === null ? null : this.coerceId(endText, line);
|
|
1005
|
+
if (start === null || end === null) {
|
|
974
1006
|
report.counts.skippedEdges++;
|
|
975
1007
|
return;
|
|
976
1008
|
}
|
|
1009
|
+
const source = qualify(start, section.startSpace);
|
|
1010
|
+
const target = qualify(end, section.endSpace);
|
|
977
1011
|
let wrongSpace: string | null = null;
|
|
978
1012
|
if (this.wrongSpace(source, section.startSpaceCode)) {
|
|
979
1013
|
wrongSpace = startText;
|
|
@@ -1024,9 +1058,9 @@ class Neo4jImportSession {
|
|
|
1024
1058
|
}
|
|
1025
1059
|
|
|
1026
1060
|
/**
|
|
1027
|
-
* Whether
|
|
1028
|
-
* endpoint's header declares
|
|
1029
|
-
* yet, is looked up by id alone.
|
|
1061
|
+
* Whether a qualified endpoint id belongs to a node a node row declared in another id space than
|
|
1062
|
+
* the endpoint's header declares (an unspaced node whose id text is `Space:id`). An endpoint
|
|
1063
|
+
* without a declared space, or a node no row declared yet, is looked up by id alone.
|
|
1030
1064
|
* @param id - the endpoint id
|
|
1031
1065
|
* @param spaceCode - the endpoint's space code, 0 for none
|
|
1032
1066
|
* @returns true when the endpoint resolves to a node of another space
|
|
@@ -1197,6 +1231,17 @@ class Neo4jImportSession {
|
|
|
1197
1231
|
}
|
|
1198
1232
|
}
|
|
1199
1233
|
|
|
1234
|
+
/**
|
|
1235
|
+
* The id a node of an id space is stored under: `Space:id`, so the same id in two spaces stays two
|
|
1236
|
+
* nodes (the core has one id space).
|
|
1237
|
+
* @param id - the coerced id cell
|
|
1238
|
+
* @param space - the id space, or null
|
|
1239
|
+
* @returns the id unchanged without a space, else the string `Space:id`
|
|
1240
|
+
*/
|
|
1241
|
+
function qualify(id: NodeId, space: string | null): NodeId {
|
|
1242
|
+
return space === null ? id : `${space}:${String(id)}`;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1200
1245
|
/**
|
|
1201
1246
|
* The value of a quoted empty cell: an empty string for a string-kind property, an empty list for
|
|
1202
1247
|
* a list property, unset for anything else (neo4j-admin's default `--ignore-empty-strings=false`).
|
|
@@ -44,7 +44,14 @@ import {
|
|
|
44
44
|
} from "./importer.js";
|
|
45
45
|
|
|
46
46
|
export { NEO4J_CAPABILITIES, neo4jExporter, type Neo4jExportOptions } from "./exporter.js";
|
|
47
|
-
export {
|
|
47
|
+
export {
|
|
48
|
+
ID_SPACE_COLUMN,
|
|
49
|
+
LABELS_COLUMN,
|
|
50
|
+
neo4jImporter,
|
|
51
|
+
type Neo4jImportOptions,
|
|
52
|
+
ORIGINAL_ID_COLUMN,
|
|
53
|
+
TYPE_COLUMN,
|
|
54
|
+
} from "./importer.js";
|
|
48
55
|
|
|
49
56
|
/**
|
|
50
57
|
* The issue codes the Neo4j importer records (design section 8.6), by name: the codes shared with
|
|
@@ -74,9 +81,9 @@ export const NEO4J_ISSUE = Object.freeze({
|
|
|
74
81
|
MISSING_ENDPOINT: MISSING_ENDPOINT_CODE,
|
|
75
82
|
/** A node id repeated in one id space (last write wins). */
|
|
76
83
|
DUPLICATE_NODE: DUPLICATE_NODE_CODE,
|
|
77
|
-
/** A node id
|
|
84
|
+
/** A spaced node id `Space:id` that equals the text of an id declared without a space. */
|
|
78
85
|
ID_SPACE_COLLISION: ID_SPACE_COLLISION_CODE,
|
|
79
|
-
/** A relationship endpoint
|
|
86
|
+
/** A spaced relationship endpoint `Space:id` that names a node declared without a space; the row is skipped. */
|
|
80
87
|
ENDPOINT_SPACE: ENDPOINT_SPACE_CODE,
|
|
81
88
|
/** Two id texts merged into one number under ids "number". */
|
|
82
89
|
ID_MERGED: ID_MERGED_CODE,
|
|
@@ -25,6 +25,7 @@ import { explicitWeights } from "../../common/weights.js";
|
|
|
25
25
|
import { encodeChunks, joinText } from "../../common/writer.js";
|
|
26
26
|
import { type CommonExportOptions, type ExportCapabilities, type GraphExporter, type LossNote } from "../../types.js";
|
|
27
27
|
import {
|
|
28
|
+
encodeCharacterReferences,
|
|
28
29
|
formatIntervals,
|
|
29
30
|
isParameterKey,
|
|
30
31
|
LABEL_COLUMN,
|
|
@@ -771,7 +772,8 @@ function* writeVertices(
|
|
|
771
772
|
const label = labelOf(labels, ids, i, parts.length > 0);
|
|
772
773
|
let line = String(i + 1);
|
|
773
774
|
if (label !== null) {
|
|
774
|
-
|
|
775
|
+
// the importer decodes `&#dddd;` in labels, so a literal one is written with `&`
|
|
776
|
+
line += ` ${quotePajekLabel(encodeCharacterReferences(label))}`;
|
|
775
777
|
}
|
|
776
778
|
if (parts.length > 0) {
|
|
777
779
|
line += ` ${parts.join(" ")}`;
|