@graphty/graph-io 0.3.40 → 0.3.42
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 +2 -0
- package/dist/chunks/{children-CL3Cy0ez.js → children-CA4coBi0.js} +36 -27
- package/dist/chunks/children-CA4coBi0.js.map +1 -0
- package/dist/chunks/{choose-CS3fvGT1.js → choose-DvpbdRuz.js} +2 -2
- package/dist/chunks/{choose-CS3fvGT1.js.map → choose-DvpbdRuz.js.map} +1 -1
- package/dist/chunks/{constants-DqKZT6cH.js → constants-fT3E8JYY.js} +2 -2
- package/dist/chunks/{constants-DqKZT6cH.js.map → constants-fT3E8JYY.js.map} +1 -1
- package/dist/chunks/{escape-BJRLd3vJ.js → escape-B62ZGM_a.js} +2 -2
- package/dist/chunks/{escape-BJRLd3vJ.js.map → escape-B62ZGM_a.js.map} +1 -1
- package/dist/chunks/{export-B-aVCwyG.js → export-rKLeGQMo.js} +404 -243
- package/dist/chunks/export-rKLeGQMo.js.map +1 -0
- package/dist/chunks/{exporter-6ocL_9x_.js → exporter-8hLNemFk.js} +4 -4
- package/dist/chunks/{exporter-6ocL_9x_.js.map → exporter-8hLNemFk.js.map} +1 -1
- package/dist/chunks/{exporter-BVXnV9-9.js → exporter-BWwEuOPT.js} +29 -17
- package/dist/chunks/exporter-BWwEuOPT.js.map +1 -0
- package/dist/chunks/{exporter-Bcp-l4Bo.js → exporter-CpRyHZ3H.js} +574 -475
- package/dist/chunks/exporter-CpRyHZ3H.js.map +1 -0
- package/dist/chunks/{exporter-SFYkDzD0.js → exporter-yLxKBvLE.js} +23 -20
- package/dist/chunks/{exporter-SFYkDzD0.js.map → exporter-yLxKBvLE.js.map} +1 -1
- package/dist/chunks/{importer-DcKB97kW.js → importer-BbNb7eNg.js} +111 -71
- package/dist/chunks/importer-BbNb7eNg.js.map +1 -0
- package/dist/chunks/{importer-D_srHyj6.js → importer-C98dpSH4.js} +5 -5
- package/dist/chunks/{importer-D_srHyj6.js.map → importer-C98dpSH4.js.map} +1 -1
- package/dist/chunks/{importer-BJlGAOht.js → importer-D794XUTe.js} +3 -3
- package/dist/chunks/{importer-BJlGAOht.js.map → importer-D794XUTe.js.map} +1 -1
- package/dist/chunks/{importer-AjwOJrn2.js → importer-Dz7tI8Sa.js} +202 -181
- package/dist/chunks/importer-Dz7tI8Sa.js.map +1 -0
- package/dist/chunks/{importer-DEp_fYjf.js → importer-Ht7LNg8L.js} +92 -64
- package/dist/chunks/importer-Ht7LNg8L.js.map +1 -0
- package/dist/chunks/{importer-CPqnIY8m.js → importer-WrGRZb0N.js} +42 -29
- package/dist/chunks/importer-WrGRZb0N.js.map +1 -0
- package/dist/chunks/{importer-BpuS3J8C.js → importer-xZ-3suY1.js} +74 -61
- package/dist/chunks/importer-xZ-3suY1.js.map +1 -0
- package/dist/chunks/{json-elements-DpO6AXIc.js → json-elements-DKSqpNW1.js} +3 -3
- package/dist/chunks/{json-elements-DpO6AXIc.js.map → json-elements-DKSqpNW1.js.map} +1 -1
- package/dist/chunks/{ontology-export-50L9DOV4.js → ontology-export-CtRXjg_i.js} +2 -2
- package/dist/chunks/{ontology-export-50L9DOV4.js.map → ontology-export-CtRXjg_i.js.map} +1 -1
- package/dist/chunks/{records-r3SFHz11.js → records-Cu4bxYOm.js} +69 -36
- package/dist/chunks/records-Cu4bxYOm.js.map +1 -0
- package/dist/chunks/{text-Bjfa1xYL.js → text-DHOJXnED.js} +20 -11
- package/dist/chunks/text-DHOJXnED.js.map +1 -0
- package/dist/csv.js +323 -241
- package/dist/csv.js.map +1 -1
- package/dist/cx.js +1 -1
- package/dist/cx2.js +2 -2
- package/dist/cys.js +1 -1
- package/dist/dot.js +1 -1
- package/dist/gexf.js +195 -97
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +170 -149
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +27 -27
- package/dist/graphml.js +2 -2
- package/dist/json.js +1 -1
- package/dist/neo4j.js +103 -65
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/children.d.ts.map +1 -1
- package/dist/src/children.js +50 -27
- package/dist/src/children.js.map +1 -1
- package/dist/src/common/attributes.d.ts.map +1 -1
- package/dist/src/common/attributes.js +86 -42
- package/dist/src/common/attributes.js.map +1 -1
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +358 -184
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/lists.js +30 -15
- package/dist/src/common/lists.js.map +1 -1
- package/dist/src/common/text.d.ts +7 -0
- package/dist/src/common/text.d.ts.map +1 -1
- package/dist/src/common/text.js +15 -7
- package/dist/src/common/text.js.map +1 -1
- package/dist/src/common/zip.d.ts.map +1 -1
- package/dist/src/common/zip.js +36 -19
- package/dist/src/common/zip.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +288 -146
- package/dist/src/formats/csv/exporter.js.map +1 -1
- package/dist/src/formats/csv/importer.d.ts.map +1 -1
- package/dist/src/formats/csv/importer.js +129 -80
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/records.d.ts.map +1 -1
- package/dist/src/formats/csv/records.js +78 -36
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/cys/tables.d.ts.map +1 -1
- package/dist/src/formats/cys/tables.js +11 -8
- package/dist/src/formats/cys/tables.js.map +1 -1
- package/dist/src/formats/dot/tokenizer.d.ts +39 -0
- package/dist/src/formats/dot/tokenizer.d.ts.map +1 -1
- package/dist/src/formats/dot/tokenizer.js +112 -66
- package/dist/src/formats/dot/tokenizer.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +65 -37
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +138 -50
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +51 -35
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +103 -58
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/syntax.d.ts.map +1 -1
- package/dist/src/formats/gml/syntax.js +17 -8
- package/dist/src/formats/gml/syntax.js.map +1 -1
- package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
- package/dist/src/formats/graphml/exporter.js +34 -24
- package/dist/src/formats/graphml/exporter.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +17 -13
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/graphml/tree.js +23 -14
- package/dist/src/formats/graphml/tree.js.map +1 -1
- package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
- package/dist/src/formats/graphml/yfiles.js +22 -10
- package/dist/src/formats/graphml/yfiles.js.map +1 -1
- package/dist/src/formats/json/dialect.d.ts.map +1 -1
- package/dist/src/formats/json/dialect.js +54 -20
- 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 +38 -21
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +536 -343
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/json/obographs.js +29 -17
- package/dist/src/formats/json/obographs.js.map +1 -1
- package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
- package/dist/src/formats/neo4j/exporter.js +73 -43
- package/dist/src/formats/neo4j/exporter.js.map +1 -1
- package/dist/src/formats/neo4j/header.d.ts.map +1 -1
- package/dist/src/formats/neo4j/header.js +37 -16
- package/dist/src/formats/neo4j/header.js.map +1 -1
- package/dist/src/formats/neo4j/importer.d.ts.map +1 -1
- package/dist/src/formats/neo4j/importer.js +21 -11
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/obo/exporter.d.ts.map +1 -1
- package/dist/src/formats/obo/exporter.js +18 -7
- package/dist/src/formats/obo/exporter.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +142 -73
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +26 -15
- package/dist/src/formats/pajek/importer.js.map +1 -1
- package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
- package/dist/src/formats/pajek/syntax.js +85 -58
- package/dist/src/formats/pajek/syntax.js.map +1 -1
- package/dist/src/formats/xgmml/emit.d.ts +7 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
- package/dist/src/formats/xgmml/emit.js +79 -40
- package/dist/src/formats/xgmml/emit.js.map +1 -1
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
- package/dist/src/formats/xgmml/exporter.js +38 -12
- package/dist/src/formats/xgmml/exporter.js.map +1 -1
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
- package/dist/src/formats/xgmml/importer.js +38 -18
- package/dist/src/formats/xgmml/importer.js.map +1 -1
- package/dist/src/sniff.js +109 -57
- package/dist/src/sniff.js.map +1 -1
- package/dist/xgmml.js +2 -2
- package/package.json +2 -2
- package/src/children.ts +54 -27
- package/src/common/attributes.ts +118 -49
- package/src/common/input.ts +439 -197
- package/src/common/lists.ts +29 -15
- package/src/common/text.ts +24 -15
- package/src/common/zip.ts +47 -18
- package/src/formats/csv/exporter.ts +377 -175
- package/src/formats/csv/importer.ts +155 -84
- package/src/formats/csv/records.ts +90 -35
- package/src/formats/cys/tables.ts +11 -10
- package/src/formats/dot/tokenizer.ts +118 -68
- package/src/formats/gexf/exporter.ts +67 -36
- package/src/formats/gexf/importer.ts +173 -63
- package/src/formats/gml/exporter.ts +78 -82
- package/src/formats/gml/importer.ts +119 -68
- package/src/formats/gml/syntax.ts +18 -8
- package/src/formats/graphml/exporter.ts +44 -24
- package/src/formats/graphml/importer.ts +18 -11
- package/src/formats/graphml/tree.ts +22 -12
- package/src/formats/graphml/yfiles.ts +23 -10
- package/src/formats/json/dialect.ts +61 -22
- package/src/formats/json/exporter.ts +40 -21
- package/src/formats/json/importer.ts +701 -385
- package/src/formats/json/obographs.ts +35 -15
- package/src/formats/neo4j/exporter.ts +79 -41
- package/src/formats/neo4j/header.ts +50 -16
- package/src/formats/neo4j/importer.ts +21 -10
- package/src/formats/obo/exporter.ts +34 -17
- package/src/formats/pajek/exporter.ts +180 -117
- package/src/formats/pajek/importer.ts +27 -15
- package/src/formats/pajek/syntax.ts +99 -58
- package/src/formats/xgmml/emit.ts +88 -40
- package/src/formats/xgmml/exporter.ts +40 -12
- package/src/formats/xgmml/importer.ts +45 -23
- package/src/sniff.ts +115 -59
- package/dist/chunks/children-CL3Cy0ez.js.map +0 -1
- package/dist/chunks/export-B-aVCwyG.js.map +0 -1
- package/dist/chunks/exporter-BVXnV9-9.js.map +0 -1
- package/dist/chunks/exporter-Bcp-l4Bo.js.map +0 -1
- package/dist/chunks/importer-AjwOJrn2.js.map +0 -1
- package/dist/chunks/importer-BpuS3J8C.js.map +0 -1
- package/dist/chunks/importer-CPqnIY8m.js.map +0 -1
- package/dist/chunks/importer-DEp_fYjf.js.map +0 -1
- package/dist/chunks/importer-DcKB97kW.js.map +0 -1
- package/dist/chunks/records-r3SFHz11.js.map +0 -1
- package/dist/chunks/text-Bjfa1xYL.js.map +0 -1
|
@@ -87,6 +87,16 @@ function isDigit(c: number): boolean {
|
|
|
87
87
|
return c >= 48 && c <= 57;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Whether a code unit is a blank between tokens: space, tab, vertical tab, form feed, or a U+FEFF
|
|
92
|
+
* after the start (concatenated files), which the shared text check reports.
|
|
93
|
+
* @param c - the code unit
|
|
94
|
+
* @returns true for a blank
|
|
95
|
+
*/
|
|
96
|
+
function isBlank(c: number): boolean {
|
|
97
|
+
return c === 0x20 || c === 0x09 || c === 0x0b || c === 0x0c || c === BOM_CODE;
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
/**
|
|
91
101
|
* A pull lexer over one DOT document with arbitrary lookahead.
|
|
92
102
|
* @category Plugin helpers
|
|
@@ -247,40 +257,68 @@ export class DotTokenizer {
|
|
|
247
257
|
this.pos = i + 1;
|
|
248
258
|
return { kind: "id", text: out, quoted: true, html: false, line };
|
|
249
259
|
}
|
|
250
|
-
if (c === 0x5c) {
|
|
251
|
-
|
|
252
|
-
if (
|
|
253
|
-
out +=
|
|
254
|
-
i += 2;
|
|
255
|
-
segment = i;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (d === 0x5c) {
|
|
259
|
-
// Graphviz's scanner consumes a backslash pair as one unit (both kept), so the
|
|
260
|
-
// second backslash never escapes a closing quote: "x\\" is the text x\\
|
|
261
|
-
i += 2;
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
if (d === 0x0a || d === 0x0d) {
|
|
265
|
-
out += text.slice(segment, i);
|
|
266
|
-
i += d === 0x0d && text.charCodeAt(i + 2) === 0x0a ? 3 : 2;
|
|
267
|
-
this.line++;
|
|
268
|
-
segment = i;
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
i++;
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
if (c === 0x0a || c === 0x0d) {
|
|
275
|
-
if (c === 0x0d && text.charCodeAt(i + 1) === 0x0a) {
|
|
276
|
-
i++;
|
|
260
|
+
if (c === 0x5c && this.isQuotedSplice(i)) {
|
|
261
|
+
out += text.slice(segment, i);
|
|
262
|
+
if (text.codePointAt(i + 1) === 0x22) {
|
|
263
|
+
out += '"';
|
|
277
264
|
}
|
|
278
|
-
this.
|
|
265
|
+
i = this.quotedSpliceEnd(i);
|
|
266
|
+
segment = i;
|
|
267
|
+
} else {
|
|
268
|
+
i = this.quotedStep(i, c);
|
|
279
269
|
}
|
|
280
|
-
i++;
|
|
281
270
|
}
|
|
282
271
|
}
|
|
283
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Whether the backslash at `i` inside a quoted string starts a sequence that changes the decoded
|
|
275
|
+
* text: `\"` (an escaped quote) or a backslash before a line break (a line continuation).
|
|
276
|
+
* @param i - the offset of the backslash
|
|
277
|
+
* @returns true for an escaped quote or a line continuation
|
|
278
|
+
*/
|
|
279
|
+
private isQuotedSplice(i: number): boolean {
|
|
280
|
+
const d = this.text.codePointAt(i + 1);
|
|
281
|
+
return d === 0x22 || d === 0x0a || d === 0x0d;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The offset just past an escaped quote or a line continuation, counting the line a
|
|
286
|
+
* continuation ends.
|
|
287
|
+
* @param i - the offset of the backslash
|
|
288
|
+
* @returns the offset after the sequence
|
|
289
|
+
*/
|
|
290
|
+
private quotedSpliceEnd(i: number): number {
|
|
291
|
+
const { text } = this;
|
|
292
|
+
const d = text.charCodeAt(i + 1);
|
|
293
|
+
if (d === 0x22) {
|
|
294
|
+
return i + 2;
|
|
295
|
+
}
|
|
296
|
+
this.line++;
|
|
297
|
+
return d === 0x0d && text.codePointAt(i + 2) === 0x0a ? i + 3 : i + 2;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Step over one unit of a quoted string kept as written: a backslash pair (consumed as one unit,
|
|
302
|
+
* as Graphviz's scanner does, so the second backslash never escapes a closing quote: "x\\" is
|
|
303
|
+
* the text x\\), a lone backslash, a raw line break (counted) or any other character.
|
|
304
|
+
* @param i - the offset of the unit
|
|
305
|
+
* @param c - the code unit at `i`
|
|
306
|
+
* @returns the offset after the unit
|
|
307
|
+
*/
|
|
308
|
+
private quotedStep(i: number, c: number): number {
|
|
309
|
+
const { text } = this;
|
|
310
|
+
if (c === 0x5c) {
|
|
311
|
+
return text.codePointAt(i + 1) === 0x5c ? i + 2 : i + 1;
|
|
312
|
+
}
|
|
313
|
+
if (c === 0x0a) {
|
|
314
|
+
this.line++;
|
|
315
|
+
} else if (c === 0x0d) {
|
|
316
|
+
this.line++;
|
|
317
|
+
return text.codePointAt(i + 1) === 0x0a ? i + 2 : i + 1;
|
|
318
|
+
}
|
|
319
|
+
return i + 1;
|
|
320
|
+
}
|
|
321
|
+
|
|
284
322
|
/**
|
|
285
323
|
* Lex an HTML string starting at the opening angle bracket: the text up to the matching
|
|
286
324
|
* closing bracket, brackets balanced, kept verbatim including the outer pair.
|
|
@@ -313,51 +351,63 @@ export class DotTokenizer {
|
|
|
313
351
|
*/
|
|
314
352
|
private skipTrivia(): void {
|
|
315
353
|
const { text } = this;
|
|
316
|
-
|
|
317
|
-
if (this.pos >= text.length) {
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
354
|
+
while (this.pos < text.length) {
|
|
320
355
|
const c = text.charCodeAt(this.pos);
|
|
321
|
-
if (c
|
|
322
|
-
this.line++;
|
|
323
|
-
this.pos++;
|
|
324
|
-
this.skipPreprocessorLine();
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
if (c === 0x0d) {
|
|
328
|
-
this.line++;
|
|
329
|
-
this.pos += text.charCodeAt(this.pos + 1) === 0x0a ? 2 : 1;
|
|
330
|
-
this.skipPreprocessorLine();
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
if (c === 0x20 || c === 0x09 || c === 0x0b || c === 0x0c) {
|
|
334
|
-
this.pos++;
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
if (c === BOM_CODE) {
|
|
338
|
-
// a U+FEFF after the start (concatenated files) is whitespace; the shared text check reports it
|
|
356
|
+
if (isBlank(c)) {
|
|
339
357
|
this.pos++;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (c === 0x2f) {
|
|
343
|
-
const d = text.charCodeAt(this.pos + 1);
|
|
344
|
-
if (d === 0x2f) {
|
|
345
|
-
this.skipToLineEnd();
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
if (d === 0x2a) {
|
|
349
|
-
this.skipBlockComment();
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
if (c === 0x23 && this.pos === 0) {
|
|
354
|
-
this.skipToLineEnd();
|
|
355
|
-
continue;
|
|
358
|
+
} else if (!this.skipBreakOrComment(c)) {
|
|
359
|
+
return;
|
|
356
360
|
}
|
|
357
|
-
return;
|
|
358
361
|
}
|
|
359
362
|
}
|
|
360
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Skip one line break, comment or leading `#` line at the current position.
|
|
366
|
+
* @param c - the code unit at the current position
|
|
367
|
+
* @returns false when the current position starts a token
|
|
368
|
+
*/
|
|
369
|
+
private skipBreakOrComment(c: number): boolean {
|
|
370
|
+
if (c === 0x0a || c === 0x0d) {
|
|
371
|
+
this.skipLineBreak(c);
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
if (c === 0x2f) {
|
|
375
|
+
return this.skipComment();
|
|
376
|
+
}
|
|
377
|
+
if (c === 0x23 && this.pos === 0) {
|
|
378
|
+
this.skipToLineEnd();
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Consume the line break at the current position (CR LF as one) and a `#` line after it.
|
|
386
|
+
* @param c - the code unit at the current position, LF or CR
|
|
387
|
+
*/
|
|
388
|
+
private skipLineBreak(c: number): void {
|
|
389
|
+
this.line++;
|
|
390
|
+
this.pos += c === 0x0d && this.text.codePointAt(this.pos + 1) === 0x0a ? 2 : 1;
|
|
391
|
+
this.skipPreprocessorLine();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Skip a `//` or block comment starting at the current `/`.
|
|
396
|
+
* @returns false when the `/` starts no comment
|
|
397
|
+
*/
|
|
398
|
+
private skipComment(): boolean {
|
|
399
|
+
const d = this.text.codePointAt(this.pos + 1);
|
|
400
|
+
if (d === 0x2f) {
|
|
401
|
+
this.skipToLineEnd();
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
if (d === 0x2a) {
|
|
405
|
+
this.skipBlockComment();
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
|
|
361
411
|
/**
|
|
362
412
|
* Discard a `#` line when one starts at the current position (the start of a line).
|
|
363
413
|
*/
|
|
@@ -1247,6 +1247,27 @@ function lifetimeAttrs(roles: RoleColumns, row: number, plan: ExportPlan): strin
|
|
|
1247
1247
|
return out;
|
|
1248
1248
|
}
|
|
1249
1249
|
|
|
1250
|
+
/**
|
|
1251
|
+
* The `start` and `end` attributes of one `<spell>`.
|
|
1252
|
+
* @param s - the start, or a non-finite value for an unbounded start
|
|
1253
|
+
* @param e - the end, or a non-finite value for an unbounded end
|
|
1254
|
+
* @param open - the open-bound bits
|
|
1255
|
+
* @param plan - the plan
|
|
1256
|
+
* @returns the attribute text, each with a leading space
|
|
1257
|
+
*/
|
|
1258
|
+
function spellBounds(s: number, e: number, open: number, plan: ExportPlan): string {
|
|
1259
|
+
let attrs = "";
|
|
1260
|
+
if (Number.isFinite(s)) {
|
|
1261
|
+
const name = (open & OPEN_START) === 0 ? "start" : "startopen";
|
|
1262
|
+
attrs += ` ${name}="${escapeXmlAttribute(formatTimeValue(s, plan.timeFormat))}"`;
|
|
1263
|
+
}
|
|
1264
|
+
if (Number.isFinite(e)) {
|
|
1265
|
+
const name = (open & OPEN_END) === 0 ? "end" : "endopen";
|
|
1266
|
+
attrs += ` ${name}="${escapeXmlAttribute(formatTimeValue(e, plan.timeFormat))}"`;
|
|
1267
|
+
}
|
|
1268
|
+
return attrs;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1250
1271
|
/**
|
|
1251
1272
|
* The `<spells>` element of one element: its spells column, plus (1.2) its timestamps as [t, t].
|
|
1252
1273
|
* In 1.2 a spell whose spells.open bits mark a bound open writes that bound as `startopen` /
|
|
@@ -1276,16 +1297,7 @@ function spellsElement(roles: RoleColumns, row: number, plan: ExportPlan, indent
|
|
|
1276
1297
|
}
|
|
1277
1298
|
let out = `${indent}<spells>\n`;
|
|
1278
1299
|
for (const [s, e, open] of pairs) {
|
|
1279
|
-
|
|
1280
|
-
if (Number.isFinite(s)) {
|
|
1281
|
-
const name = (open & OPEN_START) === 0 ? "start" : "startopen";
|
|
1282
|
-
attrs += ` ${name}="${escapeXmlAttribute(formatTimeValue(s, plan.timeFormat))}"`;
|
|
1283
|
-
}
|
|
1284
|
-
if (Number.isFinite(e)) {
|
|
1285
|
-
const name = (open & OPEN_END) === 0 ? "end" : "endopen";
|
|
1286
|
-
attrs += ` ${name}="${escapeXmlAttribute(formatTimeValue(e, plan.timeFormat))}"`;
|
|
1287
|
-
}
|
|
1288
|
-
out += `${indent} <spell${attrs}/>\n`;
|
|
1300
|
+
out += `${indent} <spell${spellBounds(s, e, open, plan)}/>\n`;
|
|
1289
1301
|
}
|
|
1290
1302
|
return `${out}${indent}</spells>\n`;
|
|
1291
1303
|
}
|
|
@@ -1676,23 +1688,12 @@ function metaElement(meta: GraphSnapshot["meta"]): string {
|
|
|
1676
1688
|
}
|
|
1677
1689
|
|
|
1678
1690
|
/**
|
|
1679
|
-
* The `<graph>`
|
|
1691
|
+
* The `idtype` attribute of the `<graph>` tag.
|
|
1680
1692
|
* @param snapshot - the snapshot
|
|
1681
|
-
* @
|
|
1682
|
-
* @returns the line
|
|
1693
|
+
* @returns the attribute text with a leading space, or an empty string
|
|
1683
1694
|
*/
|
|
1684
|
-
function
|
|
1685
|
-
|
|
1686
|
-
let attrs = ` defaultedgetype="${defaultEdgeType(snapshot)}"`;
|
|
1687
|
-
let mode = meta.mode ?? "static";
|
|
1688
|
-
if (plan.temporal && mode === "static") {
|
|
1689
|
-
mode = "dynamic";
|
|
1690
|
-
}
|
|
1691
|
-
if (plan.version === "1.2" && mode === "slice") {
|
|
1692
|
-
mode = "dynamic";
|
|
1693
|
-
}
|
|
1694
|
-
attrs += ` mode="${mode}"`;
|
|
1695
|
-
let { idType } = meta;
|
|
1695
|
+
function graphIdTypeAttr(snapshot: GraphSnapshot): string {
|
|
1696
|
+
let { idType } = snapshot.meta;
|
|
1696
1697
|
if (idType === null) {
|
|
1697
1698
|
const { kind } = snapshot.ids;
|
|
1698
1699
|
if (kind === "string") {
|
|
@@ -1701,17 +1702,16 @@ function graphStart(snapshot: GraphSnapshot, plan: ExportPlan): string {
|
|
|
1701
1702
|
idType = "integer";
|
|
1702
1703
|
}
|
|
1703
1704
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
}
|
|
1705
|
+
return idType === "integer" || idType === "string" ? ` idtype="${idType}"` : "";
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* The `start`, `end` and `timestamp` attributes of the `<graph>` tag.
|
|
1710
|
+
* @param snapshot - the snapshot
|
|
1711
|
+
* @param plan - the plan
|
|
1712
|
+
* @returns the attribute text with leading spaces, or an empty string
|
|
1713
|
+
*/
|
|
1714
|
+
function graphHeaderAttrs(snapshot: GraphSnapshot, plan: ExportPlan): string {
|
|
1715
1715
|
const timestamp = graphExtraText(snapshot, "timestamp");
|
|
1716
1716
|
const header: Record<string, string | null> = {
|
|
1717
1717
|
start: graphExtraText(snapshot, "start"),
|
|
@@ -1724,11 +1724,42 @@ function graphStart(snapshot: GraphSnapshot, plan: ExportPlan): string {
|
|
|
1724
1724
|
header.end ??= timestamp;
|
|
1725
1725
|
header.timestamp = null;
|
|
1726
1726
|
}
|
|
1727
|
+
let attrs = "";
|
|
1727
1728
|
for (const [key, value] of Object.entries(header)) {
|
|
1728
1729
|
if (value !== null) {
|
|
1729
1730
|
attrs += ` ${key}="${escapeXmlAttribute(value)}"`;
|
|
1730
1731
|
}
|
|
1731
1732
|
}
|
|
1733
|
+
return attrs;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
* The `<graph>` start tag with its header attributes.
|
|
1738
|
+
* @param snapshot - the snapshot
|
|
1739
|
+
* @param plan - the plan
|
|
1740
|
+
* @returns the line
|
|
1741
|
+
*/
|
|
1742
|
+
function graphStart(snapshot: GraphSnapshot, plan: ExportPlan): string {
|
|
1743
|
+
const { meta } = snapshot;
|
|
1744
|
+
let attrs = ` defaultedgetype="${defaultEdgeType(snapshot)}"`;
|
|
1745
|
+
let mode = meta.mode ?? "static";
|
|
1746
|
+
if (plan.temporal && mode === "static") {
|
|
1747
|
+
mode = "dynamic";
|
|
1748
|
+
}
|
|
1749
|
+
if (plan.version === "1.2" && mode === "slice") {
|
|
1750
|
+
mode = "dynamic";
|
|
1751
|
+
}
|
|
1752
|
+
attrs += ` mode="${mode}"`;
|
|
1753
|
+
attrs += graphIdTypeAttr(snapshot);
|
|
1754
|
+
if (plan.timeFormat !== null) {
|
|
1755
|
+
attrs += ` timeformat="${plan.timeFormat}"`;
|
|
1756
|
+
} else if (plan.temporal) {
|
|
1757
|
+
attrs += ' timeformat="double"';
|
|
1758
|
+
}
|
|
1759
|
+
if (plan.version === "1.3" && plan.timeRepresentation !== null) {
|
|
1760
|
+
attrs += ` timerepresentation="${plan.timeRepresentation}"`;
|
|
1761
|
+
}
|
|
1762
|
+
attrs += graphHeaderAttrs(snapshot, plan);
|
|
1732
1763
|
return ` <graph${attrs}>\n`;
|
|
1733
1764
|
}
|
|
1734
1765
|
|
|
@@ -1328,20 +1328,15 @@ class GexfReader implements XmlHandler {
|
|
|
1328
1328
|
}
|
|
1329
1329
|
|
|
1330
1330
|
/**
|
|
1331
|
-
*
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
* @param
|
|
1331
|
+
* Whether an attribute was already declared (warning that the first declaration is kept), and
|
|
1332
|
+
* a warning when values that use it came before its declaration.
|
|
1333
|
+
* @param domain - node or edge
|
|
1334
|
+
* @param id - the attribute id
|
|
1335
|
+
* @param where - the report location
|
|
1336
|
+
* @returns true when the declaration is a duplicate and must be skipped
|
|
1335
1337
|
*/
|
|
1336
|
-
private
|
|
1337
|
-
const { report
|
|
1338
|
-
const { domain, dynamic, attrs } = pending;
|
|
1339
|
-
const id = attrs.get("id");
|
|
1340
|
-
const where = { line: pending.line, element: id ?? null };
|
|
1341
|
-
if (id === undefined) {
|
|
1342
|
-
report.error("validation-error", ATTRIBUTE_ID_CODE, "<attribute> without an id", where);
|
|
1343
|
-
return;
|
|
1344
|
-
}
|
|
1338
|
+
private isDuplicateAttribute(domain: "node" | "edge", id: string, where: IssueLocation): boolean {
|
|
1339
|
+
const { report } = this;
|
|
1345
1340
|
const tracked = domain === "node" ? this.nodeAttrs : this.edgeAttrs;
|
|
1346
1341
|
if (tracked.has(id) || (domain === "edge" && this.weightAttr !== null && this.weightAttr.id === id)) {
|
|
1347
1342
|
report.warning(
|
|
@@ -1350,7 +1345,7 @@ class GexfReader implements XmlHandler {
|
|
|
1350
1345
|
`${domain} attribute "${id}" is declared twice; the first declaration is kept`,
|
|
1351
1346
|
where,
|
|
1352
1347
|
);
|
|
1353
|
-
return;
|
|
1348
|
+
return true;
|
|
1354
1349
|
}
|
|
1355
1350
|
if (this.unknownAttributes.has(`${domain}:${id}`)) {
|
|
1356
1351
|
report.warning(
|
|
@@ -1360,6 +1355,68 @@ class GexfReader implements XmlHandler {
|
|
|
1360
1355
|
where,
|
|
1361
1356
|
);
|
|
1362
1357
|
}
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* Whether an edge attribute becomes THE weight: the first attribute of the weight's title does
|
|
1363
|
+
* (declared here), a later one only warns and stays a plain column.
|
|
1364
|
+
* @param attribute - the attribute's domain, id, display name, title and type
|
|
1365
|
+
* @param attribute.domain - node or edge
|
|
1366
|
+
* @param attribute.id - the attribute id
|
|
1367
|
+
* @param attribute.name - the column name
|
|
1368
|
+
* @param attribute.title - the declared title, or null
|
|
1369
|
+
* @param attribute.type - the declared type, or null
|
|
1370
|
+
* @param where - the report location
|
|
1371
|
+
* @returns true when the attribute was declared as the weight
|
|
1372
|
+
*/
|
|
1373
|
+
private claimsWeight(
|
|
1374
|
+
attribute: { domain: "node" | "edge"; id: string; name: string; title: string | null; type: string | null },
|
|
1375
|
+
where: IssueLocation,
|
|
1376
|
+
): boolean {
|
|
1377
|
+
const { report, sink, options } = this;
|
|
1378
|
+
const { domain, id, name, title, type } = attribute;
|
|
1379
|
+
if (domain !== "edge" || !isWeightField(name, options.weightFrom)) {
|
|
1380
|
+
return false;
|
|
1381
|
+
}
|
|
1382
|
+
if (this.weightAttr !== null) {
|
|
1383
|
+
// the first attribute of that title stays THE weight; this one becomes a plain column
|
|
1384
|
+
report.warning(
|
|
1385
|
+
"validation-error",
|
|
1386
|
+
DUPLICATE_ATTRIBUTE_CODE,
|
|
1387
|
+
`edge attribute "${id}" is also titled ${name}; attribute "${this.weightAttr.id}" is the weight, "${id}" is kept as a column`,
|
|
1388
|
+
where,
|
|
1389
|
+
);
|
|
1390
|
+
return false;
|
|
1391
|
+
}
|
|
1392
|
+
const declared = declareAttribute({ format: GEXF_FORMAT, id, title, type, long: options.long });
|
|
1393
|
+
for (const issue of declared.issues) {
|
|
1394
|
+
report.warning(issue.category, issue.code, issue.message, where);
|
|
1395
|
+
}
|
|
1396
|
+
this.weightAttr = { id, spec: declared.spec, table: INVALID_INDEX as ExtensionHandle };
|
|
1397
|
+
sink.setMeta({ weightOrigin: { format: GEXF_FORMAT, id, title, type, namespace: null } });
|
|
1398
|
+
return true;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* Declare one `<attribute id title type>` with its `<default>` and `<options>` (design
|
|
1403
|
+
* sections 5.1, 5.5, 5.6). The edge attribute titled `weightFrom` is the weight and gets no
|
|
1404
|
+
* column; its declaration is recorded in meta.weightOrigin.
|
|
1405
|
+
* @param pending - the attribute as read
|
|
1406
|
+
*/
|
|
1407
|
+
private declareOne(pending: PendingAttribute): void {
|
|
1408
|
+
const { report, sink, options } = this;
|
|
1409
|
+
const { domain, dynamic, attrs } = pending;
|
|
1410
|
+
const id = attrs.get("id");
|
|
1411
|
+
const where = { line: pending.line, element: id ?? null };
|
|
1412
|
+
if (id === undefined) {
|
|
1413
|
+
report.error("validation-error", ATTRIBUTE_ID_CODE, "<attribute> without an id", where);
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
const tracked = domain === "node" ? this.nodeAttrs : this.edgeAttrs;
|
|
1417
|
+
if (this.isDuplicateAttribute(domain, id, where)) {
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1363
1420
|
const title = attrs.get("title") ?? null;
|
|
1364
1421
|
const type = attrs.get("type") ?? null;
|
|
1365
1422
|
if (type === null) {
|
|
@@ -1371,21 +1428,7 @@ class GexfReader implements XmlHandler {
|
|
|
1371
1428
|
);
|
|
1372
1429
|
}
|
|
1373
1430
|
const name = title !== null && title.length > 0 ? title : id;
|
|
1374
|
-
if (
|
|
1375
|
-
// the first attribute of that title stays THE weight; this one becomes a plain column
|
|
1376
|
-
report.warning(
|
|
1377
|
-
"validation-error",
|
|
1378
|
-
DUPLICATE_ATTRIBUTE_CODE,
|
|
1379
|
-
`edge attribute "${id}" is also titled ${name}; attribute "${this.weightAttr.id}" is the weight, "${id}" is kept as a column`,
|
|
1380
|
-
where,
|
|
1381
|
-
);
|
|
1382
|
-
} else if (domain === "edge" && isWeightField(name, options.weightFrom)) {
|
|
1383
|
-
const declared = declareAttribute({ format: GEXF_FORMAT, id, title, type, long: options.long });
|
|
1384
|
-
for (const issue of declared.issues) {
|
|
1385
|
-
report.warning(issue.category, issue.code, issue.message, where);
|
|
1386
|
-
}
|
|
1387
|
-
this.weightAttr = { id, spec: declared.spec, table: INVALID_INDEX as ExtensionHandle };
|
|
1388
|
-
sink.setMeta({ weightOrigin: { format: GEXF_FORMAT, id, title, type, namespace: null } });
|
|
1431
|
+
if (this.claimsWeight({ domain, id, name, title, type }, where)) {
|
|
1389
1432
|
return;
|
|
1390
1433
|
}
|
|
1391
1434
|
const declaredNames = domain === "node" ? this.declaredNodeNames : this.declaredEdgeNames;
|
|
@@ -1717,6 +1760,15 @@ class GexfReader implements XmlHandler {
|
|
|
1717
1760
|
report.recordError(err, ref.where);
|
|
1718
1761
|
}
|
|
1719
1762
|
}
|
|
1763
|
+
this.resolveParentsLists(lists);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/**
|
|
1767
|
+
* Resolve the `<parents>` lists read so far into the parents column.
|
|
1768
|
+
* @param lists - the queued `<parents>` lists
|
|
1769
|
+
*/
|
|
1770
|
+
private resolveParentsLists(lists: readonly (typeof this.parentsRefs)[number][]): void {
|
|
1771
|
+
const { sink, report } = this;
|
|
1720
1772
|
for (const ref of lists) {
|
|
1721
1773
|
const indices: number[] = [];
|
|
1722
1774
|
for (const id of ref.ids) {
|
|
@@ -1763,7 +1815,7 @@ class GexfReader implements XmlHandler {
|
|
|
1763
1815
|
* @param line - the line
|
|
1764
1816
|
*/
|
|
1765
1817
|
private beginEdge(attrs: ReadonlyMap<string, string>, line: number): void {
|
|
1766
|
-
const { report, sink
|
|
1818
|
+
const { report, sink } = this;
|
|
1767
1819
|
this.checkAbort();
|
|
1768
1820
|
this.ctx.push(Ctx.Edge);
|
|
1769
1821
|
this.edge = null;
|
|
@@ -1816,18 +1868,7 @@ class GexfReader implements XmlHandler {
|
|
|
1816
1868
|
try {
|
|
1817
1869
|
const source = this.coerceId(sourceText, where);
|
|
1818
1870
|
const target = this.coerceId(targetText, where);
|
|
1819
|
-
|
|
1820
|
-
if (sink.indexOf(source) === INVALID_INDEX) {
|
|
1821
|
-
throw new GraphFormatError("E_UNKNOWN_NODE", `edge references undeclared node "${sourceText}"`, {
|
|
1822
|
-
id: source,
|
|
1823
|
-
});
|
|
1824
|
-
}
|
|
1825
|
-
if (sink.indexOf(target) === INVALID_INDEX) {
|
|
1826
|
-
throw new GraphFormatError("E_UNKNOWN_NODE", `edge references undeclared node "${targetText}"`, {
|
|
1827
|
-
id: target,
|
|
1828
|
-
});
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1871
|
+
this.requireDeclaredEndpoints(source, target, sourceText, targetText);
|
|
1831
1872
|
const weight = this.xmlWeight(attrs, where);
|
|
1832
1873
|
const before = sink.edgeCount;
|
|
1833
1874
|
index = this.resolver.addEdge(source, target, kind, weight, where);
|
|
@@ -1846,6 +1887,47 @@ class GexfReader implements XmlHandler {
|
|
|
1846
1887
|
frame.mirror = this.resolver.lastMirror;
|
|
1847
1888
|
frame.xmlWeight = attrs.get("weight") ?? null;
|
|
1848
1889
|
this.edge = frame;
|
|
1890
|
+
this.writeEdgeColumns(attrs, index, idText, where);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
/**
|
|
1894
|
+
* Throw when addMissingNodes is off and an endpoint names an undeclared node.
|
|
1895
|
+
* @param source - the coerced source id
|
|
1896
|
+
* @param target - the coerced target id
|
|
1897
|
+
* @param sourceText - the source as written
|
|
1898
|
+
* @param targetText - the target as written
|
|
1899
|
+
*/
|
|
1900
|
+
private requireDeclaredEndpoints(source: NodeId, target: NodeId, sourceText: string, targetText: string): void {
|
|
1901
|
+
const { sink, options } = this;
|
|
1902
|
+
if (options.addMissingNodes) {
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
if (sink.indexOf(source) === INVALID_INDEX) {
|
|
1906
|
+
throw new GraphFormatError("E_UNKNOWN_NODE", `edge references undeclared node "${sourceText}"`, {
|
|
1907
|
+
id: source,
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
if (sink.indexOf(target) === INVALID_INDEX) {
|
|
1911
|
+
throw new GraphFormatError("E_UNKNOWN_NODE", `edge references undeclared node "${targetText}"`, {
|
|
1912
|
+
id: target,
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
/**
|
|
1918
|
+
* Write the id, label, kind and lifetime columns of an added edge; a failure is reported.
|
|
1919
|
+
* @param attrs - the edge's attributes
|
|
1920
|
+
* @param index - the edge's index
|
|
1921
|
+
* @param idText - the edge's id, or undefined
|
|
1922
|
+
* @param where - the edge's location
|
|
1923
|
+
*/
|
|
1924
|
+
private writeEdgeColumns(
|
|
1925
|
+
attrs: ReadonlyMap<string, string>,
|
|
1926
|
+
index: number,
|
|
1927
|
+
idText: string | undefined,
|
|
1928
|
+
where: IssueLocation,
|
|
1929
|
+
): void {
|
|
1930
|
+
const { report, sink } = this;
|
|
1849
1931
|
try {
|
|
1850
1932
|
if (idText !== undefined) {
|
|
1851
1933
|
this.edgeIdColumn.set(sink, report, index, idText, where);
|
|
@@ -2555,35 +2637,18 @@ class GexfReader implements XmlHandler {
|
|
|
2555
2637
|
case "color":
|
|
2556
2638
|
columns.color.set(sink, report, index, parseColor(attrs), where);
|
|
2557
2639
|
break;
|
|
2558
|
-
case "position":
|
|
2559
|
-
|
|
2560
|
-
const y = vizNumber(attrs, "y");
|
|
2561
|
-
const z = attrs.has("z") ? vizNumber(attrs, "z") : 0;
|
|
2562
|
-
this.positionColumn ??= new LazyColumn("node", {
|
|
2563
|
-
...NODE_DECLS.position,
|
|
2564
|
-
extra: { ...NODE_DECLS.position.extra, sourceDims: attrs.has("z") ? 3 : 2 },
|
|
2565
|
-
});
|
|
2566
|
-
this.positionColumn.set(sink, report, index, [x, y, z], where);
|
|
2640
|
+
case "position":
|
|
2641
|
+
this.writePosition(attrs, index, where);
|
|
2567
2642
|
break;
|
|
2568
|
-
}
|
|
2569
2643
|
case "size":
|
|
2570
2644
|
this.sizeColumn.set(sink, report, index, vizNumber(attrs, "value"), where);
|
|
2571
2645
|
break;
|
|
2572
2646
|
case "thickness":
|
|
2573
2647
|
this.thicknessColumn.set(sink, report, index, vizNumber(attrs, "value"), where);
|
|
2574
2648
|
break;
|
|
2575
|
-
case "shape":
|
|
2576
|
-
|
|
2577
|
-
if (value === undefined) {
|
|
2578
|
-
throw new GraphFormatError("E_COLUMN_TYPE", "<viz:shape> without a value", { element: name });
|
|
2579
|
-
}
|
|
2580
|
-
columns.shape.set(sink, report, index, value, where);
|
|
2581
|
-
const uri = attrs.get("uri");
|
|
2582
|
-
if (domain === "node" && uri !== undefined) {
|
|
2583
|
-
this.shapeUriColumn.set(sink, report, index, uri, where);
|
|
2584
|
-
}
|
|
2649
|
+
case "shape":
|
|
2650
|
+
this.writeShape(name, attrs, domain, index, where);
|
|
2585
2651
|
break;
|
|
2586
|
-
}
|
|
2587
2652
|
default:
|
|
2588
2653
|
break;
|
|
2589
2654
|
}
|
|
@@ -2595,6 +2660,51 @@ class GexfReader implements XmlHandler {
|
|
|
2595
2660
|
}
|
|
2596
2661
|
}
|
|
2597
2662
|
|
|
2663
|
+
/**
|
|
2664
|
+
* Write a `<viz:position>`.
|
|
2665
|
+
* @param attrs - its attributes
|
|
2666
|
+
* @param index - the node's index
|
|
2667
|
+
* @param where - the node's location
|
|
2668
|
+
*/
|
|
2669
|
+
private writePosition(attrs: ReadonlyMap<string, string>, index: number, where: IssueLocation): void {
|
|
2670
|
+
const x = vizNumber(attrs, "x");
|
|
2671
|
+
const y = vizNumber(attrs, "y");
|
|
2672
|
+
const z = attrs.has("z") ? vizNumber(attrs, "z") : 0;
|
|
2673
|
+
this.positionColumn ??= new LazyColumn("node", {
|
|
2674
|
+
...NODE_DECLS.position,
|
|
2675
|
+
extra: { ...NODE_DECLS.position.extra, sourceDims: attrs.has("z") ? 3 : 2 },
|
|
2676
|
+
});
|
|
2677
|
+
this.positionColumn.set(this.sink, this.report, index, [x, y, z], where);
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
/**
|
|
2681
|
+
* Write a `<viz:shape>` and, on a node, its `uri`.
|
|
2682
|
+
* @param name - the element name as written
|
|
2683
|
+
* @param attrs - its attributes
|
|
2684
|
+
* @param domain - node or edge
|
|
2685
|
+
* @param index - the element's index
|
|
2686
|
+
* @param where - the element's location
|
|
2687
|
+
*/
|
|
2688
|
+
private writeShape(
|
|
2689
|
+
name: string,
|
|
2690
|
+
attrs: ReadonlyMap<string, string>,
|
|
2691
|
+
domain: "node" | "edge",
|
|
2692
|
+
index: number,
|
|
2693
|
+
where: IssueLocation,
|
|
2694
|
+
): void {
|
|
2695
|
+
const { sink, report } = this;
|
|
2696
|
+
const columns = domain === "node" ? this.nodeColumns : this.edgeColumns;
|
|
2697
|
+
const value = attrs.get("value");
|
|
2698
|
+
if (value === undefined) {
|
|
2699
|
+
throw new GraphFormatError("E_COLUMN_TYPE", "<viz:shape> without a value", { element: name });
|
|
2700
|
+
}
|
|
2701
|
+
columns.shape.set(sink, report, index, value, where);
|
|
2702
|
+
const uri = attrs.get("uri");
|
|
2703
|
+
if (domain === "node" && uri !== undefined) {
|
|
2704
|
+
this.shapeUriColumn.set(sink, report, index, uri, where);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2598
2708
|
// ---------------------------------------------------------------- helpers
|
|
2599
2709
|
|
|
2600
2710
|
/**
|