@graphty/graph-io 0.3.4 → 0.3.5
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/dist/chunks/{escape-DXbOoLKl.js → escape-BjmIaFFo.js} +2 -2
- package/dist/chunks/{escape-DXbOoLKl.js.map → escape-BjmIaFFo.js.map} +1 -1
- package/dist/chunks/{importer-B9qF4e32.js → importer-BddBW3sf.js} +2 -2
- package/dist/chunks/{importer-B9qF4e32.js.map → importer-BddBW3sf.js.map} +1 -1
- package/dist/chunks/{importer-xoHpRG5x.js → importer-BtHFICnf.js} +2 -2
- package/dist/chunks/{importer-xoHpRG5x.js.map → importer-BtHFICnf.js.map} +1 -1
- package/dist/chunks/{importer-C2caVvl6.js → importer-DfI8POXb.js} +272 -63
- package/dist/chunks/importer-DfI8POXb.js.map +1 -0
- package/dist/csv.js +1 -1
- package/dist/dot.js +1 -1
- package/dist/gexf.js +1 -1
- package/dist/gml.js +1 -1
- package/dist/graph-io.js +9 -8
- package/dist/graphml.js +1 -1
- package/dist/neo4j.js +51 -16
- package/dist/neo4j.js.map +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/escape.d.ts +4 -3
- package/dist/src/common/escape.d.ts.map +1 -1
- package/dist/src/common/escape.js +5 -4
- package/dist/src/common/escape.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts +3 -1
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +34 -6
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts +16 -3
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +49 -11
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/neo4j/index.d.ts +3 -3
- package/dist/src/formats/neo4j/index.d.ts.map +1 -1
- package/dist/src/formats/neo4j/index.js +3 -3
- package/dist/src/formats/neo4j/index.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +3 -2
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts +14 -8
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +233 -61
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts +39 -8
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +81 -11
- package/dist/src/formats/pajek/syntax.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 +1 -1
- package/dist/src/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunks/importer-C2caVvl6.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GraphFormatError, INVALID_INDEX } from "@graphty/graph-format";
|
|
2
|
-
import { L as LOSS, ax as joinText, ak as encodeChunks, aE as resolveExportOptions, aG as sanitizeIds, ad as checkCapabilities, az as pairFolding, ac as capabilities, al as explicitWeights, ao as formatF64, am as formatDecimal, an as formatF32, aq as formatInteger, ab as canonicalId, Y as SINK_OPTION_CODE, P as OPTION_IGNORED_CODE, X as ROLE_TAKEN_CODE, U as RENAMED_CODE, o as ID_MERGED_CODE, k as DUPLICATE_NODE_CODE, $ as SYNTAX_CODE, A as MULTIPLE_GRAPHS_CODE, a5 as UNKNOWN_ENCODING_CODE, n as ENCODING_FALLBACK_CODE, s as INVALID_ENCODING_CODE, u as INVALID_UTF8_CODE, aF as resolveImportOptions, I as ImportReportBuilder, aD as reportUnusedOptions, x as LineReader, a as throwIfAborted, aC as reportSinkOptions, l as DirectionResolver, v as IdCoercer,
|
|
3
|
-
import { i as isPajekLabel, q as quotePajekLabel } from "./escape-
|
|
2
|
+
import { L as LOSS, ax as joinText, ak as encodeChunks, aE as resolveExportOptions, aG as sanitizeIds, ad as checkCapabilities, az as pairFolding, ac as capabilities, al as explicitWeights, ao as formatF64, am as formatDecimal, an as formatF32, aq as formatInteger, ab as canonicalId, Y as SINK_OPTION_CODE, P as OPTION_IGNORED_CODE, X as ROLE_TAKEN_CODE, U as RENAMED_CODE, o as ID_MERGED_CODE, k as DUPLICATE_NODE_CODE, $ as SYNTAX_CODE, A as MULTIPLE_GRAPHS_CODE, a5 as UNKNOWN_ENCODING_CODE, n as ENCODING_FALLBACK_CODE, s as INVALID_ENCODING_CODE, u as INVALID_UTF8_CODE, aF as resolveImportOptions, I as ImportReportBuilder, aD as reportUnusedOptions, x as LineReader, a as throwIfAborted, aC as reportSinkOptions, l as DirectionResolver, v as IdCoercer, aL as uniqueColumnName, ai as declareResolved, af as coerceIdText, aw as isWeightField, aA as parseWeightText } from "./writer-BdMak4_J.js";
|
|
3
|
+
import { i as isPajekLabel, q as quotePajekLabel } from "./escape-BjmIaFFo.js";
|
|
4
4
|
import { i as inferTextDtype, W as WIDENING_UNSUPPORTED_CODE, a as isNumericText, T as TextCellWriter } from "./text-Dr0Ifpag.js";
|
|
5
5
|
const LABEL_COLUMN = "label";
|
|
6
6
|
const POSITION_COLUMN = "position";
|
|
@@ -9,7 +9,22 @@ const RELATION_COLUMN = "relation";
|
|
|
9
9
|
const SPELLS_COLUMN = "spells";
|
|
10
10
|
const VALUE_COLUMN = "value";
|
|
11
11
|
const VALUE_FIELD = "value";
|
|
12
|
-
const
|
|
12
|
+
const PARTITION_COLUMN = "partition";
|
|
13
|
+
const VECTOR_COLUMN = "vector";
|
|
14
|
+
const SHAPES = /* @__PURE__ */ new Set([
|
|
15
|
+
"ellipse",
|
|
16
|
+
"box",
|
|
17
|
+
"diamond",
|
|
18
|
+
"triangle",
|
|
19
|
+
"cross",
|
|
20
|
+
"empty",
|
|
21
|
+
"house",
|
|
22
|
+
"man",
|
|
23
|
+
"woman"
|
|
24
|
+
]);
|
|
25
|
+
function isShapeKeyword(token) {
|
|
26
|
+
return SHAPES.has(token.toLowerCase());
|
|
27
|
+
}
|
|
13
28
|
const ORIGINAL_ID_KEY = "graphty_originalId";
|
|
14
29
|
const SECTION_KINDS = /* @__PURE__ */ new Map([
|
|
15
30
|
["network", "network"],
|
|
@@ -18,7 +33,9 @@ const SECTION_KINDS = /* @__PURE__ */ new Map([
|
|
|
18
33
|
["edges", "edges"],
|
|
19
34
|
["arcslist", "arcslist"],
|
|
20
35
|
["edgeslist", "edgeslist"],
|
|
21
|
-
["matrix", "matrix"]
|
|
36
|
+
["matrix", "matrix"],
|
|
37
|
+
["partition", "partition"],
|
|
38
|
+
["vector", "vector"]
|
|
22
39
|
]);
|
|
23
40
|
const INTEGER_TEXT = /^[+-]?[0-9]+$/;
|
|
24
41
|
const TIME_POINT = /^(\*|[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)(?:-(\*|[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?))?$/;
|
|
@@ -34,6 +51,15 @@ function tokenize(line) {
|
|
|
34
51
|
started = true;
|
|
35
52
|
continue;
|
|
36
53
|
}
|
|
54
|
+
if (!quoted && !started && c === 91) {
|
|
55
|
+
const close = line.indexOf("]", i);
|
|
56
|
+
if (close > i && line.lastIndexOf("[", close) === i) {
|
|
57
|
+
current += line.slice(i, close + 1);
|
|
58
|
+
started = true;
|
|
59
|
+
i = close;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
37
63
|
if (!quoted && (c === 32 || c === 9 || c === 13 || c === 12 || c === 11)) {
|
|
38
64
|
if (started) {
|
|
39
65
|
tokens.push(current);
|
|
@@ -112,6 +138,8 @@ function parseSectionHeader(line) {
|
|
|
112
138
|
}
|
|
113
139
|
break;
|
|
114
140
|
case "network":
|
|
141
|
+
case "partition":
|
|
142
|
+
case "vector":
|
|
115
143
|
if (i < tokens.length) {
|
|
116
144
|
name = tokens.slice(i).join(" ");
|
|
117
145
|
i = tokens.length;
|
|
@@ -148,11 +176,11 @@ function isIntervalToken(token) {
|
|
|
148
176
|
function parseIntervals(token) {
|
|
149
177
|
const body = token.slice(1, -1);
|
|
150
178
|
if (body.trim().length === 0) {
|
|
151
|
-
|
|
179
|
+
return [];
|
|
152
180
|
}
|
|
153
181
|
const spells = [];
|
|
154
182
|
for (const part of body.split(",")) {
|
|
155
|
-
const match = TIME_POINT.exec(part.
|
|
183
|
+
const match = TIME_POINT.exec(part.replace(/\s+/g, ""));
|
|
156
184
|
if (match === null) {
|
|
157
185
|
throw new Error(`malformed time interval ${token}: "${part}" is not a-b, a-* or a`);
|
|
158
186
|
}
|
|
@@ -173,6 +201,22 @@ function parseIntervals(token) {
|
|
|
173
201
|
}
|
|
174
202
|
return spells;
|
|
175
203
|
}
|
|
204
|
+
const CHARACTER_REFERENCE = /&#(?:[xX]([0-9a-fA-F]{1,6})|([0-9]{1,7}));/g;
|
|
205
|
+
function decodeCharacterReferences(text) {
|
|
206
|
+
if (!text.includes("&#")) {
|
|
207
|
+
return text;
|
|
208
|
+
}
|
|
209
|
+
return text.replace(CHARACTER_REFERENCE, (whole, hex, dec) => {
|
|
210
|
+
const code = hex === void 0 ? Number(dec) : Number.parseInt(hex, 16);
|
|
211
|
+
return code <= 1114111 ? String.fromCodePoint(code) : whole;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
function encodeCharacterReferences(text) {
|
|
215
|
+
if (!text.includes("&#")) {
|
|
216
|
+
return text;
|
|
217
|
+
}
|
|
218
|
+
return text.replace(CHARACTER_REFERENCE, (whole) => `&${whole.slice(1)}`);
|
|
219
|
+
}
|
|
176
220
|
function formatIntervals(spells) {
|
|
177
221
|
const parts = [];
|
|
178
222
|
for (const [start, end] of spells) {
|
|
@@ -189,7 +233,7 @@ function isParameterKey(text) {
|
|
|
189
233
|
if (text.length === 0 || /[\s"]/.test(text) || text.startsWith("[") || text.startsWith("*")) {
|
|
190
234
|
return false;
|
|
191
235
|
}
|
|
192
|
-
if (
|
|
236
|
+
if (isShapeKeyword(text)) {
|
|
193
237
|
return false;
|
|
194
238
|
}
|
|
195
239
|
return !/^[+-]?(\.[0-9]|[0-9])/.test(text);
|
|
@@ -741,7 +785,7 @@ function* writeVertices(snapshot, ids, nodePlan, mangle) {
|
|
|
741
785
|
const label = labelOf(labels, ids, i, parts.length > 0);
|
|
742
786
|
let line = String(i + 1);
|
|
743
787
|
if (label !== null) {
|
|
744
|
-
line += ` ${quotePajekLabel(label)}`;
|
|
788
|
+
line += ` ${quotePajekLabel(encodeCharacterReferences(label))}`;
|
|
745
789
|
}
|
|
746
790
|
if (parts.length > 0) {
|
|
747
791
|
line += ` ${parts.join(" ")}`;
|
|
@@ -971,7 +1015,7 @@ const PAJEK_ISSUE = Object.freeze({
|
|
|
971
1015
|
UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
|
|
972
1016
|
/** Fatal: no `*Vertices` section (an empty file, or not a Pajek network). */
|
|
973
1017
|
NO_VERTICES: "E_PAJEK_NO_VERTICES",
|
|
974
|
-
/** Fatal: `*Vertices` without a vertex count,
|
|
1018
|
+
/** Fatal: `*Vertices` without a vertex count, one the sink cannot hold, or a first-mode count outside 0..N. */
|
|
975
1019
|
VERTICES_COUNT: "E_PAJEK_VERTICES_COUNT",
|
|
976
1020
|
/** A project file holds several networks; import() reads the first, importAll() reads every one. */
|
|
977
1021
|
MULTIPLE_GRAPHS: MULTIPLE_GRAPHS_CODE,
|
|
@@ -985,20 +1029,24 @@ const PAJEK_ISSUE = Object.freeze({
|
|
|
985
1029
|
VERTEX_LINE: "E_PAJEK_VERTEX_LINE",
|
|
986
1030
|
/** A vertex number outside the declared range. */
|
|
987
1031
|
VERTEX_RANGE: "E_PAJEK_VERTEX_RANGE",
|
|
988
|
-
/** Fewer vertex lines than `*Vertices` declares (vertices without a line have no label). */
|
|
989
|
-
VERTEX_COUNT: "
|
|
1032
|
+
/** Fewer vertex lines than `*Vertices` declares, which the Pajek manual allows (vertices without a line have no label). */
|
|
1033
|
+
VERTEX_COUNT: "W_PAJEK_VERTEX_COUNT",
|
|
990
1034
|
/** A second line for the same vertex; the later values overwrite. */
|
|
991
1035
|
DUPLICATE_NODE: DUPLICATE_NODE_CODE,
|
|
992
|
-
/** A line (arc, edge, list or
|
|
1036
|
+
/** A line (arc, edge, list, matrix row, partition or vector value) the grammar does not accept. */
|
|
993
1037
|
LINE: "E_PAJEK_LINE",
|
|
994
1038
|
/** A line endpoint outside the declared vertex range (the core's code, forwarded). */
|
|
995
1039
|
UNKNOWN_NODE: "E_UNKNOWN_NODE",
|
|
996
1040
|
/** A malformed time interval token. */
|
|
997
1041
|
INTERVAL: "E_PAJEK_INTERVAL",
|
|
998
|
-
/** A `*Matrix` section with the wrong number of rows. */
|
|
1042
|
+
/** A `*Matrix` section with the wrong number of rows (N, or N1 in a two-mode network). */
|
|
999
1043
|
MATRIX_ROWS: "E_PAJEK_MATRIX_ROWS",
|
|
1000
|
-
/**
|
|
1001
|
-
|
|
1044
|
+
/** `*Matrix` rows longer than the column count; the extra values are ignored. */
|
|
1045
|
+
MATRIX_EXTRA: "W_PAJEK_MATRIX_EXTRA",
|
|
1046
|
+
/** A `*Partition` or `*Vector` whose value count differs from the network's vertex count; values beyond it are dropped, vertices without one are unset. */
|
|
1047
|
+
OBJECT_COUNT: "E_PAJEK_OBJECT_COUNT",
|
|
1048
|
+
/** A project-file section (`*Events`, `*Permutation`, ...) the importer does not read; its lines are skipped. */
|
|
1049
|
+
UNSUPPORTED_SECTION: "W_PAJEK_UNSUPPORTED_SECTION",
|
|
1002
1050
|
/** Tokens after a section header the grammar does not account for. */
|
|
1003
1051
|
HEADER_EXTRA: "W_PAJEK_HEADER_EXTRA",
|
|
1004
1052
|
/** The file declares vertices but no line section. */
|
|
@@ -1099,6 +1147,11 @@ class LineError extends Error {
|
|
|
1099
1147
|
this.code = code;
|
|
1100
1148
|
}
|
|
1101
1149
|
}
|
|
1150
|
+
function isI32(value) {
|
|
1151
|
+
return Number.isInteger(value) && value >= -2147483648 && value <= 2147483647;
|
|
1152
|
+
}
|
|
1153
|
+
const LIST_VERTEX = /^-?[0-9]+$/;
|
|
1154
|
+
const RELATION_PREFIX = /^([0-9]+):$/;
|
|
1102
1155
|
class PajekParser {
|
|
1103
1156
|
/**
|
|
1104
1157
|
* Create the parser for one import call.
|
|
@@ -1119,6 +1172,11 @@ class PajekParser {
|
|
|
1119
1172
|
this.nodesCreated = false;
|
|
1120
1173
|
this.verticesDone = false;
|
|
1121
1174
|
this.sawLineSection = false;
|
|
1175
|
+
this.firstKind = "directed";
|
|
1176
|
+
this.directionSet = false;
|
|
1177
|
+
this.relationNames = /* @__PURE__ */ new Map();
|
|
1178
|
+
this.objects = [];
|
|
1179
|
+
this.objectHeader = false;
|
|
1122
1180
|
this.indexOfPos = new Uint32Array(0);
|
|
1123
1181
|
this.idOfPos = null;
|
|
1124
1182
|
this.seen = new Uint8Array(0);
|
|
@@ -1152,9 +1210,11 @@ class PajekParser {
|
|
|
1152
1210
|
if (isCommentOrBlank(text)) {
|
|
1153
1211
|
return false;
|
|
1154
1212
|
}
|
|
1213
|
+
const afterObjectHeader = this.objectHeader;
|
|
1214
|
+
this.objectHeader = false;
|
|
1155
1215
|
try {
|
|
1156
1216
|
if (isSectionLine(text)) {
|
|
1157
|
-
this.header(text, line);
|
|
1217
|
+
this.header(text, line, afterObjectHeader);
|
|
1158
1218
|
return this.ended;
|
|
1159
1219
|
}
|
|
1160
1220
|
switch (this.section) {
|
|
@@ -1176,6 +1236,9 @@ class PajekParser {
|
|
|
1176
1236
|
case "matrix":
|
|
1177
1237
|
this.matrixLine(text, line);
|
|
1178
1238
|
return false;
|
|
1239
|
+
case "object":
|
|
1240
|
+
this.objectLine(text);
|
|
1241
|
+
return false;
|
|
1179
1242
|
case "skip":
|
|
1180
1243
|
return false;
|
|
1181
1244
|
default: {
|
|
@@ -1209,7 +1272,10 @@ class PajekParser {
|
|
|
1209
1272
|
"the file declares vertices but no *Arcs, *Edges, *Arcslist, *Edgeslist or *Matrix section"
|
|
1210
1273
|
);
|
|
1211
1274
|
this.resolver.setHeader(this.options.defaultDirected);
|
|
1275
|
+
} else if (!this.directionSet) {
|
|
1276
|
+
this.resolver.setHeader(this.firstKind === "directed");
|
|
1212
1277
|
}
|
|
1278
|
+
const objects = this.writeObjects();
|
|
1213
1279
|
if (this.coercer.mergeCount > 0) {
|
|
1214
1280
|
this.report.warning(
|
|
1215
1281
|
"coercion",
|
|
@@ -1217,21 +1283,72 @@ class PajekParser {
|
|
|
1217
1283
|
`${this.coercer.mergeCount} label(s) merged into an id another label already produced under ids "number"`
|
|
1218
1284
|
);
|
|
1219
1285
|
}
|
|
1286
|
+
const pajek = {
|
|
1287
|
+
...this.firstMode === null ? {} : { firstMode: this.firstMode },
|
|
1288
|
+
...objects.length === 0 ? {} : { objects }
|
|
1289
|
+
};
|
|
1220
1290
|
const meta = {
|
|
1221
1291
|
sourceFormat: "pajek",
|
|
1222
1292
|
...this.networkName === null ? {} : { name: this.networkName },
|
|
1223
|
-
...
|
|
1293
|
+
...Object.keys(pajek).length === 0 ? {} : { extra: { pajek } }
|
|
1224
1294
|
};
|
|
1225
1295
|
this.sink.setMeta(meta);
|
|
1226
1296
|
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Write every `*Partition` / `*Vector` object into its node column: `partition` (i32) or
|
|
1299
|
+
* `vector` (f64), a later object of the same kind in `partition#2`, `vector#2`, ... The value
|
|
1300
|
+
* of vertex k is the object's k-th value.
|
|
1301
|
+
* @returns the objects' Pajek names and the columns they went to, for meta.extra.pajek.objects
|
|
1302
|
+
*/
|
|
1303
|
+
writeObjects() {
|
|
1304
|
+
const used = /* @__PURE__ */ new Set();
|
|
1305
|
+
const written = [];
|
|
1306
|
+
for (const object of this.objects) {
|
|
1307
|
+
const where = { line: object.line };
|
|
1308
|
+
const partition = object.kind === "partition";
|
|
1309
|
+
const name = uniqueColumnName(partition ? PARTITION_COLUMN : VECTOR_COLUMN, null, (n2) => used.has(n2));
|
|
1310
|
+
used.add(name);
|
|
1311
|
+
const { handle, decl } = declareResolved(
|
|
1312
|
+
this.sink,
|
|
1313
|
+
"node",
|
|
1314
|
+
{
|
|
1315
|
+
name,
|
|
1316
|
+
dtype: partition ? "i32" : "f64",
|
|
1317
|
+
nullable: true,
|
|
1318
|
+
origin: { format: "pajek", id: object.kind }
|
|
1319
|
+
},
|
|
1320
|
+
this.report,
|
|
1321
|
+
where
|
|
1322
|
+
);
|
|
1323
|
+
const n = Math.min(object.values.length, this.vertexCount);
|
|
1324
|
+
for (let pos = 0; pos < n; pos++) {
|
|
1325
|
+
const value = object.values[pos];
|
|
1326
|
+
if (value !== null) {
|
|
1327
|
+
this.sink.setNodeValue(handle, this.indexOfPos[pos], value);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
const declared = object.count ?? object.values.length;
|
|
1331
|
+
if (declared !== this.vertexCount || object.values.length !== declared) {
|
|
1332
|
+
this.report.error(
|
|
1333
|
+
"validation-error",
|
|
1334
|
+
PAJEK_ISSUE.OBJECT_COUNT,
|
|
1335
|
+
`*${partition ? "Partition" : "Vector"} "${object.name}" declares ${declared} vertices and holds ${object.values.length} value(s); the network has ${this.vertexCount}`,
|
|
1336
|
+
where
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
written.push({ kind: object.kind, name: object.name, column: decl.name });
|
|
1340
|
+
}
|
|
1341
|
+
return written;
|
|
1342
|
+
}
|
|
1227
1343
|
// ============================================================ headers
|
|
1228
1344
|
/**
|
|
1229
1345
|
* A section header line.
|
|
1230
1346
|
* @param text - the line
|
|
1231
1347
|
* @param line - the line number
|
|
1348
|
+
* @param afterObjectHeader - the previous line was a `*Partition`, `*Vector` or skipped section header
|
|
1232
1349
|
*/
|
|
1233
|
-
header(text, line) {
|
|
1234
|
-
const
|
|
1350
|
+
header(text, line, afterObjectHeader) {
|
|
1351
|
+
const previous = this.section;
|
|
1235
1352
|
this.endSection();
|
|
1236
1353
|
const h = parseSectionHeader(text);
|
|
1237
1354
|
if (h === null) {
|
|
@@ -1256,8 +1373,11 @@ class PajekParser {
|
|
|
1256
1373
|
this.section = "none";
|
|
1257
1374
|
return;
|
|
1258
1375
|
case "vertices":
|
|
1259
|
-
if (
|
|
1260
|
-
this.section =
|
|
1376
|
+
if (afterObjectHeader) {
|
|
1377
|
+
this.section = previous;
|
|
1378
|
+
if (previous === "object") {
|
|
1379
|
+
this.objects[this.objects.length - 1].count = h.count;
|
|
1380
|
+
}
|
|
1261
1381
|
return;
|
|
1262
1382
|
}
|
|
1263
1383
|
if (this.vertexCount >= 0) {
|
|
@@ -1278,9 +1398,16 @@ class PajekParser {
|
|
|
1278
1398
|
this.lineSection(h, line, "matrix", "directed");
|
|
1279
1399
|
this.matrixRows = 0;
|
|
1280
1400
|
return;
|
|
1401
|
+
case "partition":
|
|
1402
|
+
case "vector":
|
|
1403
|
+
this.objects.push({ kind: h.kind, name: h.name ?? "", line, count: null, values: [] });
|
|
1404
|
+
this.section = "object";
|
|
1405
|
+
this.objectHeader = true;
|
|
1406
|
+
return;
|
|
1281
1407
|
case "unsupported":
|
|
1282
1408
|
this.section = "skip";
|
|
1283
|
-
this.
|
|
1409
|
+
this.objectHeader = true;
|
|
1410
|
+
this.report.warning(
|
|
1284
1411
|
"unsupported",
|
|
1285
1412
|
PAJEK_ISSUE.UNSUPPORTED_SECTION,
|
|
1286
1413
|
`the *${h.keyword} section is not supported; its lines are skipped`,
|
|
@@ -1305,6 +1432,13 @@ class PajekParser {
|
|
|
1305
1432
|
if (!Number.isSafeInteger(h.count)) {
|
|
1306
1433
|
this.report.fail(PAJEK_ISSUE.VERTICES_COUNT, `*Vertices ${h.count}: not a count`, { line });
|
|
1307
1434
|
}
|
|
1435
|
+
if (h.secondCount !== null && (h.secondCount < 0 || h.secondCount > h.count)) {
|
|
1436
|
+
this.report.fail(
|
|
1437
|
+
PAJEK_ISSUE.VERTICES_COUNT,
|
|
1438
|
+
`*Vertices ${h.count} ${h.secondCount}: the first-mode count is not within 0..${h.count}`,
|
|
1439
|
+
{ line }
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1308
1442
|
try {
|
|
1309
1443
|
this.sink.reserve(h.count);
|
|
1310
1444
|
} catch (err) {
|
|
@@ -1333,8 +1467,9 @@ class PajekParser {
|
|
|
1333
1467
|
}
|
|
1334
1468
|
}
|
|
1335
1469
|
/**
|
|
1336
|
-
* A line section header: the vertex set is completed
|
|
1337
|
-
* (rule 1 of design section 8.4)
|
|
1470
|
+
* A line section header: the vertex set is completed and the kind switched. The sink's
|
|
1471
|
+
* direction (rule 1 of design section 8.4) is set by the first LINE, not the first header, so
|
|
1472
|
+
* an empty `*Arcs` before the `*Edges` of an undirected network does not make it directed.
|
|
1338
1473
|
* @param h - the header
|
|
1339
1474
|
* @param line - the line number
|
|
1340
1475
|
* @param section - the parser state for the section
|
|
@@ -1351,21 +1486,25 @@ class PajekParser {
|
|
|
1351
1486
|
this.finishVertices();
|
|
1352
1487
|
if (!this.sawLineSection) {
|
|
1353
1488
|
this.sawLineSection = true;
|
|
1354
|
-
this.
|
|
1489
|
+
this.firstKind = kind;
|
|
1355
1490
|
}
|
|
1356
1491
|
this.section = section;
|
|
1357
1492
|
this.kind = kind;
|
|
1358
1493
|
this.relation = h.relation === null ? null : h.name ?? String(h.relation);
|
|
1494
|
+
if (h.relation !== null) {
|
|
1495
|
+
this.relationNames.set(h.relation, this.relation ?? String(h.relation));
|
|
1496
|
+
}
|
|
1359
1497
|
}
|
|
1360
1498
|
/**
|
|
1361
1499
|
* Checks that run when a section ends.
|
|
1362
1500
|
*/
|
|
1363
1501
|
endSection() {
|
|
1364
|
-
|
|
1502
|
+
const rows = this.firstMode ?? this.vertexCount;
|
|
1503
|
+
if (this.section === "matrix" && this.matrixRows !== rows) {
|
|
1365
1504
|
this.report.error(
|
|
1366
1505
|
"validation-error",
|
|
1367
1506
|
PAJEK_ISSUE.MATRIX_ROWS,
|
|
1368
|
-
`*Matrix has ${this.matrixRows} row(s); *Vertices declares ${
|
|
1507
|
+
`*Matrix has ${this.matrixRows} row(s); *Vertices declares ${rows}`
|
|
1369
1508
|
);
|
|
1370
1509
|
}
|
|
1371
1510
|
this.section = "none";
|
|
@@ -1462,7 +1601,7 @@ class PajekParser {
|
|
|
1462
1601
|
}
|
|
1463
1602
|
const lines = this.seenCount + this.report.counts.skippedNodes;
|
|
1464
1603
|
if (this.seenCount > 0 && lines < n) {
|
|
1465
|
-
this.report.
|
|
1604
|
+
this.report.warning(
|
|
1466
1605
|
"validation-error",
|
|
1467
1606
|
PAJEK_ISSUE.VERTEX_COUNT,
|
|
1468
1607
|
`*Vertices declares ${n} vertices but ${lines} vertex line(s) were read; the others have no label`
|
|
@@ -1498,8 +1637,9 @@ class PajekParser {
|
|
|
1498
1637
|
);
|
|
1499
1638
|
}
|
|
1500
1639
|
let i = 1;
|
|
1501
|
-
|
|
1502
|
-
if (
|
|
1640
|
+
let label = null;
|
|
1641
|
+
if (tokens.length > 1 && !startsWithCoordinates(text, tokens)) {
|
|
1642
|
+
label = decodeCharacterReferences(tokens[1]);
|
|
1503
1643
|
i = 2;
|
|
1504
1644
|
}
|
|
1505
1645
|
let x = 0;
|
|
@@ -1525,8 +1665,8 @@ class PajekParser {
|
|
|
1525
1665
|
);
|
|
1526
1666
|
}
|
|
1527
1667
|
let shape = null;
|
|
1528
|
-
if (i < tokens.length &&
|
|
1529
|
-
shape = tokens[i];
|
|
1668
|
+
if (i < tokens.length && isShapeKeyword(tokens[i])) {
|
|
1669
|
+
shape = tokens[i].toLowerCase();
|
|
1530
1670
|
i++;
|
|
1531
1671
|
}
|
|
1532
1672
|
const extras = this.extras(tokens, i, "node");
|
|
@@ -1684,7 +1824,14 @@ class PajekParser {
|
|
|
1684
1824
|
* @param line - the line number
|
|
1685
1825
|
*/
|
|
1686
1826
|
edgeLine(text, line) {
|
|
1687
|
-
|
|
1827
|
+
let tokens = this.tokens(text, "edge");
|
|
1828
|
+
let { relation } = this;
|
|
1829
|
+
const prefix = RELATION_PREFIX.exec(tokens[0]);
|
|
1830
|
+
if (prefix !== null) {
|
|
1831
|
+
const k = Number(prefix[1]);
|
|
1832
|
+
relation = this.relationNames.get(k) ?? String(k);
|
|
1833
|
+
tokens = tokens.slice(1);
|
|
1834
|
+
}
|
|
1688
1835
|
if (tokens.length < 2 || !isVertexNumber(tokens[0]) || !isVertexNumber(tokens[1])) {
|
|
1689
1836
|
this.report.counts.skippedEdges++;
|
|
1690
1837
|
throw new LineError(
|
|
@@ -1722,17 +1869,18 @@ class PajekParser {
|
|
|
1722
1869
|
if (valueText !== null && !valueIsWeight) {
|
|
1723
1870
|
this.writeParameter("edge", VALUE_COLUMN, e, valueText);
|
|
1724
1871
|
}
|
|
1725
|
-
this.writeEdgeExtras(e, extras);
|
|
1872
|
+
this.writeEdgeExtras(e, extras, relation);
|
|
1726
1873
|
}
|
|
1727
1874
|
/**
|
|
1728
|
-
* An adjacency list line of `*Arcslist` / `*Edgeslist`: `v n1 n2
|
|
1875
|
+
* An adjacency list line of `*Arcslist` / `*Edgeslist`: `v n1 n2 ...`; a negative vertex
|
|
1876
|
+
* number is read as its absolute value, as Pajek does.
|
|
1729
1877
|
* @param text - the line
|
|
1730
1878
|
* @param line - the line number
|
|
1731
1879
|
*/
|
|
1732
1880
|
listLine(text, line) {
|
|
1733
1881
|
const tokens = this.tokens(text, "edge");
|
|
1734
1882
|
for (const token of tokens) {
|
|
1735
|
-
if (!
|
|
1883
|
+
if (!LIST_VERTEX.test(token)) {
|
|
1736
1884
|
this.report.counts.skippedEdges++;
|
|
1737
1885
|
throw new LineError(
|
|
1738
1886
|
"parse-error",
|
|
@@ -1741,15 +1889,17 @@ class PajekParser {
|
|
|
1741
1889
|
);
|
|
1742
1890
|
}
|
|
1743
1891
|
}
|
|
1744
|
-
const u = this.endpoint(Number(tokens[0]));
|
|
1892
|
+
const u = this.endpoint(Math.abs(Number(tokens[0])));
|
|
1745
1893
|
for (let i = 1; i < tokens.length; i++) {
|
|
1746
|
-
const v = this.endpoint(Number(tokens[i]));
|
|
1894
|
+
const v = this.endpoint(Math.abs(Number(tokens[i])));
|
|
1747
1895
|
const e = this.pushEdge(u, v, void 0, line);
|
|
1748
1896
|
this.writeRelation(e);
|
|
1749
1897
|
}
|
|
1750
1898
|
}
|
|
1751
1899
|
/**
|
|
1752
|
-
* A `*Matrix` row
|
|
1900
|
+
* A `*Matrix` row, every non-zero entry an arc from the row's vertex with that value: N values
|
|
1901
|
+
* in a one-mode network; in a two-mode network (`*Vertices N N1`) the matrix is N1 rows of
|
|
1902
|
+
* N - N1 values, entry j of row i an arc from vertex i to vertex N1 + j.
|
|
1753
1903
|
* @param text - the line
|
|
1754
1904
|
* @param line - the line number
|
|
1755
1905
|
*/
|
|
@@ -1757,38 +1907,71 @@ class PajekParser {
|
|
|
1757
1907
|
const tokens = this.tokens(text, "edge");
|
|
1758
1908
|
const row = this.matrixRows;
|
|
1759
1909
|
this.matrixRows++;
|
|
1760
|
-
|
|
1910
|
+
const offset = this.firstMode ?? 0;
|
|
1911
|
+
const rows = this.firstMode ?? this.vertexCount;
|
|
1912
|
+
const columns = this.vertexCount - offset;
|
|
1913
|
+
if (row >= rows) {
|
|
1761
1914
|
throw new LineError(
|
|
1762
1915
|
"validation-error",
|
|
1763
1916
|
PAJEK_ISSUE.MATRIX_ROWS,
|
|
1764
|
-
`*Matrix row ${row + 1} is beyond the ${
|
|
1917
|
+
`*Matrix row ${row + 1} is beyond the ${rows} declared row(s)`
|
|
1765
1918
|
);
|
|
1766
1919
|
}
|
|
1767
|
-
if (tokens.length
|
|
1920
|
+
if (tokens.length < columns) {
|
|
1768
1921
|
throw new LineError(
|
|
1769
1922
|
"parse-error",
|
|
1770
1923
|
PAJEK_ISSUE.LINE,
|
|
1771
|
-
`*Matrix row ${row + 1} has ${tokens.length} value(s);
|
|
1924
|
+
`*Matrix row ${row + 1} has ${tokens.length} value(s); the matrix has ${columns} column(s)`
|
|
1772
1925
|
);
|
|
1773
1926
|
}
|
|
1774
|
-
for (
|
|
1775
|
-
if (!isNumericText(
|
|
1927
|
+
for (let j = 0; j < columns; j++) {
|
|
1928
|
+
if (!isNumericText(tokens[j])) {
|
|
1776
1929
|
throw new LineError(
|
|
1777
1930
|
"parse-error",
|
|
1778
1931
|
PAJEK_ISSUE.LINE,
|
|
1779
|
-
`*Matrix row ${row + 1}: "${
|
|
1932
|
+
`*Matrix row ${row + 1}: "${tokens[j]}" is not a number`
|
|
1780
1933
|
);
|
|
1781
1934
|
}
|
|
1782
1935
|
}
|
|
1783
|
-
|
|
1936
|
+
if (tokens.length > columns) {
|
|
1937
|
+
this.report.warnOnce(
|
|
1938
|
+
"validation-error",
|
|
1939
|
+
PAJEK_ISSUE.MATRIX_EXTRA,
|
|
1940
|
+
`*Matrix row ${row + 1} has ${tokens.length} value(s) for ${columns} column(s); the extra values are ignored`,
|
|
1941
|
+
{ line }
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1944
|
+
for (let j = 0; j < columns; j++) {
|
|
1784
1945
|
const value = Number(tokens[j]);
|
|
1785
1946
|
if (value === 0) {
|
|
1786
1947
|
continue;
|
|
1787
1948
|
}
|
|
1788
|
-
const e = this.pushEdge(row, j, value, line);
|
|
1949
|
+
const e = this.pushEdge(row, offset + j, value, line);
|
|
1789
1950
|
this.writeRelation(e);
|
|
1790
1951
|
}
|
|
1791
1952
|
}
|
|
1953
|
+
/**
|
|
1954
|
+
* A value line of a `*Partition` (integers) or `*Vector` (numbers): the values of the next
|
|
1955
|
+
* vertices, usually one per line.
|
|
1956
|
+
* @param text - the line
|
|
1957
|
+
*/
|
|
1958
|
+
objectLine(text) {
|
|
1959
|
+
const object = this.objects[this.objects.length - 1];
|
|
1960
|
+
const tokens = this.tokens(text, "node");
|
|
1961
|
+
for (const token of tokens) {
|
|
1962
|
+
if (!isNumericText(token) || object.kind === "partition" && !isI32(Number(token))) {
|
|
1963
|
+
object.values.push(null);
|
|
1964
|
+
throw new LineError(
|
|
1965
|
+
"parse-error",
|
|
1966
|
+
PAJEK_ISSUE.LINE,
|
|
1967
|
+
`*${object.kind === "partition" ? "Partition" : "Vector"} "${object.name}": "${token}" is not ${object.kind === "partition" ? "an integer" : "a number"}`
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
for (const token of tokens) {
|
|
1972
|
+
object.values.push(Number(token));
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1792
1975
|
/**
|
|
1793
1976
|
* Push one line through the direction resolver, counting the logical edges it produced.
|
|
1794
1977
|
* @param u - the source position
|
|
@@ -1799,6 +1982,10 @@ class PajekParser {
|
|
|
1799
1982
|
*/
|
|
1800
1983
|
pushEdge(u, v, weight, line) {
|
|
1801
1984
|
const { sink } = this;
|
|
1985
|
+
if (!this.directionSet) {
|
|
1986
|
+
this.directionSet = true;
|
|
1987
|
+
this.resolver.setHeader(this.kind === "directed", { line });
|
|
1988
|
+
}
|
|
1802
1989
|
const before = sink.edgeCount;
|
|
1803
1990
|
const e = this.resolver.addEdge(this.idAt(u), this.idAt(v), this.kind, weight, { line });
|
|
1804
1991
|
this.report.counts.edges += sink.edgeCount - before;
|
|
@@ -1808,10 +1995,11 @@ class PajekParser {
|
|
|
1808
1995
|
* Write the relation, parameters and spells of a line on its primary half.
|
|
1809
1996
|
* @param e - the edge index
|
|
1810
1997
|
* @param extras - the parsed extras
|
|
1998
|
+
* @param relation - the line's relation name, or null
|
|
1811
1999
|
*/
|
|
1812
|
-
writeEdgeExtras(e, extras) {
|
|
2000
|
+
writeEdgeExtras(e, extras, relation) {
|
|
1813
2001
|
const { sink } = this;
|
|
1814
|
-
this.writeRelation(e);
|
|
2002
|
+
this.writeRelation(e, relation);
|
|
1815
2003
|
for (let p = 0; p < extras.keys.length; p++) {
|
|
1816
2004
|
this.writeParameter("edge", extras.keys[p], e, extras.values[p]);
|
|
1817
2005
|
}
|
|
@@ -1823,17 +2011,18 @@ class PajekParser {
|
|
|
1823
2011
|
}
|
|
1824
2012
|
}
|
|
1825
2013
|
/**
|
|
1826
|
-
* Write
|
|
2014
|
+
* Write a relation name on an edge: the current section's, unless a line prefix named another.
|
|
1827
2015
|
* @param e - the edge index
|
|
2016
|
+
* @param relation - the relation name, or null for none
|
|
1828
2017
|
*/
|
|
1829
|
-
writeRelation(e) {
|
|
1830
|
-
if (
|
|
2018
|
+
writeRelation(e, relation = this.relation) {
|
|
2019
|
+
if (relation === null) {
|
|
1831
2020
|
return;
|
|
1832
2021
|
}
|
|
1833
2022
|
if (this.relationHandle === INVALID_INDEX) {
|
|
1834
2023
|
this.relationHandle = declareResolved(this.sink, "edge", RELATION_DECL, this.report).handle;
|
|
1835
2024
|
}
|
|
1836
|
-
this.sink.setEdgeValue(this.relationHandle, e,
|
|
2025
|
+
this.sink.setEdgeValue(this.relationHandle, e, relation);
|
|
1837
2026
|
}
|
|
1838
2027
|
/**
|
|
1839
2028
|
* A line endpoint: the vertex number checked against the declared range; a reference outside
|
|
@@ -1900,7 +2089,8 @@ class PajekParser {
|
|
|
1900
2089
|
const token = tokens[i];
|
|
1901
2090
|
if (isIntervalToken(token)) {
|
|
1902
2091
|
try {
|
|
1903
|
-
|
|
2092
|
+
const spells = parseIntervals(token);
|
|
2093
|
+
extras.spells = spells.length === 0 ? null : spells;
|
|
1904
2094
|
} catch (err) {
|
|
1905
2095
|
this.skip(domain);
|
|
1906
2096
|
throw new LineError(
|
|
@@ -1961,6 +2151,16 @@ class PajekParser {
|
|
|
1961
2151
|
writer.write(row, text);
|
|
1962
2152
|
}
|
|
1963
2153
|
}
|
|
2154
|
+
function startsWithCoordinates(text, tokens) {
|
|
2155
|
+
if (text.trimStart().slice(tokens[0].length).trimStart().startsWith('"')) {
|
|
2156
|
+
return false;
|
|
2157
|
+
}
|
|
2158
|
+
let run = 0;
|
|
2159
|
+
while (1 + run < tokens.length && isNumericText(tokens[1 + run])) {
|
|
2160
|
+
run++;
|
|
2161
|
+
}
|
|
2162
|
+
return run === 2;
|
|
2163
|
+
}
|
|
1964
2164
|
function firstVertexOption(value) {
|
|
1965
2165
|
if (value === void 0) {
|
|
1966
2166
|
return "auto";
|
|
@@ -2040,7 +2240,8 @@ const pajekImporter = Object.freeze({
|
|
|
2040
2240
|
},
|
|
2041
2241
|
/**
|
|
2042
2242
|
* Read every network of a Pajek project file (`.paj`), each into its own sink. A network starts
|
|
2043
|
-
* at `*Network` or at a `*Vertices` that does not belong to a `*Partition` / `*Vector` section
|
|
2243
|
+
* at `*Network` or at a `*Vertices` that does not belong to a `*Partition` / `*Vector` section
|
|
2244
|
+
* (the `*Vertices N` line right after an object's header).
|
|
2044
2245
|
* @param input - the text, bytes or stream
|
|
2045
2246
|
* @param sinkFor - the sink of the network with this index, called before its first push
|
|
2046
2247
|
* @param options - common and Pajek options
|
|
@@ -2082,27 +2283,35 @@ class NetworkCounter {
|
|
|
2082
2283
|
this.count = 0;
|
|
2083
2284
|
this.named = false;
|
|
2084
2285
|
this.populated = false;
|
|
2085
|
-
this.
|
|
2286
|
+
this.afterObjectHeader = false;
|
|
2086
2287
|
}
|
|
2087
2288
|
/**
|
|
2088
2289
|
* Feed one line.
|
|
2089
2290
|
* @param text - the line
|
|
2090
2291
|
*/
|
|
2091
2292
|
line(text) {
|
|
2293
|
+
if (isCommentOrBlank(text)) {
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
const { afterObjectHeader } = this;
|
|
2297
|
+
this.afterObjectHeader = false;
|
|
2092
2298
|
if (!isSectionLine(text)) {
|
|
2093
2299
|
return;
|
|
2094
2300
|
}
|
|
2095
2301
|
const h = parseSectionHeader(text);
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2302
|
+
if (h === null) {
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
const { kind } = h;
|
|
2306
|
+
if (kind === "partition" || kind === "vector" || kind === "unsupported") {
|
|
2307
|
+
this.afterObjectHeader = true;
|
|
2308
|
+
} else if (kind === "network") {
|
|
2100
2309
|
if (this.count === 0 || this.named || this.populated) {
|
|
2101
2310
|
this.count++;
|
|
2102
2311
|
this.populated = false;
|
|
2103
2312
|
}
|
|
2104
2313
|
this.named = true;
|
|
2105
|
-
} else if (kind === "vertices" && !
|
|
2314
|
+
} else if (kind === "vertices" && !afterObjectHeader) {
|
|
2106
2315
|
if (this.count === 0 || this.populated) {
|
|
2107
2316
|
this.count++;
|
|
2108
2317
|
this.named = false;
|
|
@@ -2117,4 +2326,4 @@ export {
|
|
|
2117
2326
|
pajekImporter as b,
|
|
2118
2327
|
pajekExporter as p
|
|
2119
2328
|
};
|
|
2120
|
-
//# sourceMappingURL=importer-
|
|
2329
|
+
//# sourceMappingURL=importer-DfI8POXb.js.map
|