@graphty/graph-io 0.2.4 → 0.3.0
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 +29 -7
- package/dist/chunks/{escape-DyI8JofU.js → escape-CKied3Ri.js} +16 -10
- package/dist/chunks/escape-CKied3Ri.js.map +1 -0
- package/dist/chunks/{importer-DbnGYr3_.js → importer-C6QcIRIb.js} +129 -39
- package/dist/chunks/importer-C6QcIRIb.js.map +1 -0
- package/dist/chunks/{importer-CpCpfbxr.js → importer-D5pAsweZ.js} +136 -31
- package/dist/chunks/importer-D5pAsweZ.js.map +1 -0
- package/dist/chunks/{importer-GozH8DkN.js → importer-DbMrAR_w.js} +10 -4
- package/dist/chunks/importer-DbMrAR_w.js.map +1 -0
- package/dist/chunks/{importer-CQnJuWJw.js → importer-DkzjTvHc.js} +82 -40
- package/dist/chunks/importer-DkzjTvHc.js.map +1 -0
- package/dist/chunks/{records-CGpxszm1.js → records-BKSMowhR.js} +3 -3
- package/dist/chunks/{records-CGpxszm1.js.map → records-BKSMowhR.js.map} +1 -1
- package/dist/chunks/{text-CajMdVFy.js → text-Dr0Ifpag.js} +2 -2
- package/dist/chunks/{text-CajMdVFy.js.map → text-Dr0Ifpag.js.map} +1 -1
- package/dist/chunks/{writer-DxSKC7TL.js → writer-BdMak4_J.js} +341 -112
- package/dist/chunks/writer-BdMak4_J.js.map +1 -0
- package/dist/csv.js +24 -6
- package/dist/csv.js.map +1 -1
- package/dist/dot.js +1 -1
- package/dist/gexf.js +42 -10
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +69 -25
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +186 -129
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +1 -1
- package/dist/json.js +1 -1
- package/dist/neo4j.js +10 -4
- package/dist/neo4j.js.map +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/codes.d.ts +6 -0
- package/dist/src/common/codes.d.ts.map +1 -1
- package/dist/src/common/codes.js +6 -0
- package/dist/src/common/codes.js.map +1 -1
- package/dist/src/common/escape.d.ts +5 -4
- package/dist/src/common/escape.d.ts.map +1 -1
- package/dist/src/common/escape.js +6 -5
- package/dist/src/common/escape.js.map +1 -1
- package/dist/src/common/format.d.ts +3 -3
- package/dist/src/common/format.js +5 -5
- package/dist/src/common/format.js.map +1 -1
- package/dist/src/common/input.d.ts +24 -6
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +259 -23
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/options.d.ts +2 -0
- package/dist/src/common/options.d.ts.map +1 -1
- package/dist/src/common/options.js +17 -0
- package/dist/src/common/options.js.map +1 -1
- package/dist/src/common/text.js +4 -4
- package/dist/src/common/text.js.map +1 -1
- package/dist/src/common/weights.d.ts.map +1 -1
- package/dist/src/common/weights.js +8 -0
- package/dist/src/common/weights.js.map +1 -1
- package/dist/src/common/xml.d.ts +7 -0
- package/dist/src/common/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +12 -0
- package/dist/src/common/xml.js.map +1 -1
- package/dist/src/formats/csv/exporter.d.ts +5 -1
- package/dist/src/formats/csv/exporter.d.ts.map +1 -1
- package/dist/src/formats/csv/exporter.js +8 -1
- 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 +1 -0
- package/dist/src/formats/csv/importer.js.map +1 -1
- package/dist/src/formats/csv/index.d.ts +6 -0
- package/dist/src/formats/csv/index.d.ts.map +1 -1
- package/dist/src/formats/csv/index.js +7 -1
- package/dist/src/formats/csv/index.js.map +1 -1
- package/dist/src/formats/csv/records.js +1 -1
- package/dist/src/formats/csv/records.js.map +1 -1
- package/dist/src/formats/dot/exporter.d.ts +1 -1
- package/dist/src/formats/dot/exporter.d.ts.map +1 -1
- package/dist/src/formats/dot/exporter.js +3 -3
- package/dist/src/formats/dot/exporter.js.map +1 -1
- package/dist/src/formats/dot/importer.d.ts +6 -0
- package/dist/src/formats/dot/importer.d.ts.map +1 -1
- package/dist/src/formats/dot/importer.js +151 -24
- package/dist/src/formats/dot/importer.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +32 -7
- 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 +2 -2
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gexf/index.d.ts +6 -0
- package/dist/src/formats/gexf/index.d.ts.map +1 -1
- package/dist/src/formats/gexf/index.js +7 -1
- package/dist/src/formats/gexf/index.js.map +1 -1
- package/dist/src/formats/gml/importer.d.ts +2 -2
- package/dist/src/formats/gml/importer.d.ts.map +1 -1
- package/dist/src/formats/gml/importer.js +66 -25
- package/dist/src/formats/gml/importer.js.map +1 -1
- package/dist/src/formats/gml/index.d.ts +7 -1
- package/dist/src/formats/gml/index.d.ts.map +1 -1
- package/dist/src/formats/gml/index.js +8 -2
- package/dist/src/formats/gml/index.js.map +1 -1
- package/dist/src/formats/graphml/constants.d.ts +6 -0
- package/dist/src/formats/graphml/constants.d.ts.map +1 -1
- package/dist/src/formats/graphml/constants.js +7 -1
- package/dist/src/formats/graphml/constants.js.map +1 -1
- package/dist/src/formats/graphml/importer.d.ts.map +1 -1
- package/dist/src/formats/graphml/importer.js +2 -2
- package/dist/src/formats/graphml/importer.js.map +1 -1
- package/dist/src/formats/json/exporter.d.ts.map +1 -1
- package/dist/src/formats/json/exporter.js +14 -5
- package/dist/src/formats/json/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts +8 -0
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +84 -37
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/neo4j/importer.js +1 -1
- package/dist/src/formats/neo4j/importer.js.map +1 -1
- package/dist/src/formats/neo4j/index.d.ts +6 -0
- package/dist/src/formats/neo4j/index.d.ts.map +1 -1
- package/dist/src/formats/neo4j/index.js +7 -1
- package/dist/src/formats/neo4j/index.js.map +1 -1
- package/dist/src/formats/pajek/exporter.d.ts +2 -0
- package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
- package/dist/src/formats/pajek/exporter.js +13 -2
- package/dist/src/formats/pajek/exporter.js.map +1 -1
- package/dist/src/formats/pajek/importer.d.ts +8 -2
- package/dist/src/formats/pajek/importer.d.ts.map +1 -1
- package/dist/src/formats/pajek/importer.js +122 -26
- package/dist/src/formats/pajek/importer.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/dist/src/registry.d.ts +26 -0
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +101 -35
- package/dist/src/registry.js.map +1 -1
- package/dist/src/sniff.d.ts +1 -1
- package/dist/src/sniff.d.ts.map +1 -1
- package/dist/src/sniff.js +6 -1
- package/dist/src/sniff.js.map +1 -1
- package/dist/src/types.d.ts +22 -3
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/package.json +4 -3
- package/src/common/codes.ts +9 -0
- package/src/common/escape.ts +6 -5
- package/src/common/format.ts +5 -5
- package/src/common/input.ts +293 -22
- package/src/common/options.ts +24 -0
- package/src/common/text.ts +4 -4
- package/src/common/weights.ts +9 -0
- package/src/common/xml.ts +14 -0
- package/src/formats/csv/exporter.ts +12 -1
- package/src/formats/csv/importer.ts +1 -0
- package/src/formats/csv/index.ts +9 -0
- package/src/formats/csv/records.ts +1 -1
- package/src/formats/dot/exporter.ts +3 -3
- package/src/formats/dot/importer.ts +172 -32
- package/src/formats/gexf/exporter.ts +38 -7
- package/src/formats/gexf/importer.ts +12 -2
- package/src/formats/gexf/index.ts +9 -0
- package/src/formats/gml/importer.ts +75 -22
- package/src/formats/gml/index.ts +10 -1
- package/src/formats/graphml/constants.ts +9 -0
- package/src/formats/graphml/importer.ts +9 -2
- package/src/formats/json/exporter.ts +14 -5
- package/src/formats/json/importer.ts +104 -36
- package/src/formats/neo4j/importer.ts +1 -1
- package/src/formats/neo4j/index.ts +9 -0
- package/src/formats/pajek/exporter.ts +19 -2
- package/src/formats/pajek/importer.ts +145 -28
- package/src/index.ts +1 -0
- package/src/registry.ts +131 -40
- package/src/sniff.ts +6 -1
- package/src/types.ts +26 -3
- package/dist/chunks/escape-DyI8JofU.js.map +0 -1
- package/dist/chunks/importer-CQnJuWJw.js.map +0 -1
- package/dist/chunks/importer-CpCpfbxr.js.map +0 -1
- package/dist/chunks/importer-DbnGYr3_.js.map +0 -1
- package/dist/chunks/importer-GozH8DkN.js.map +0 -1
- package/dist/chunks/writer-DxSKC7TL.js.map +0 -1
- package/dist/tsconfig.build.tsbuildinfo +0 -1
package/src/formats/gml/index.ts
CHANGED
|
@@ -7,12 +7,15 @@ import {
|
|
|
7
7
|
COLUMN_RENAMED_CODE,
|
|
8
8
|
DIRECTION_FORCED_CODE,
|
|
9
9
|
DIRECTION_REFUSED_CODE,
|
|
10
|
+
ENCODING_FALLBACK_CODE,
|
|
10
11
|
ID_MERGED_CODE,
|
|
12
|
+
INVALID_ENCODING_CODE,
|
|
11
13
|
INVALID_UTF8_CODE,
|
|
12
14
|
MIXED_DIRECTION_CODE,
|
|
13
15
|
OPTION_IGNORED_CODE,
|
|
14
16
|
SINK_OPTION_CODE,
|
|
15
17
|
SYNTAX_CODE,
|
|
18
|
+
UNKNOWN_ENCODING_CODE,
|
|
16
19
|
} from "../../common/codes.js";
|
|
17
20
|
import {
|
|
18
21
|
GRAPHICS_CONFLICT_CODE,
|
|
@@ -57,10 +60,16 @@ export const GML_ISSUE = Object.freeze({
|
|
|
57
60
|
SYNTAX: SYNTAX_CODE,
|
|
58
61
|
/** The input holds invalid UTF-8 (fatal). */
|
|
59
62
|
INVALID_UTF8: INVALID_UTF8_CODE,
|
|
63
|
+
/** Invalid bytes in the encoding a BOM, a declaration or the encoding option chose (fatal). */
|
|
64
|
+
INVALID_ENCODING: INVALID_ENCODING_CODE,
|
|
65
|
+
/** Bytes that are not UTF-8 and declare no encoding were read as windows-1252. */
|
|
66
|
+
ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
|
|
67
|
+
/** A declared encoding the platform cannot decode was ignored. */
|
|
68
|
+
UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
|
|
60
69
|
/** No `graph [` block (fatal). */
|
|
61
70
|
NO_GRAPH: NO_GRAPH_CODE,
|
|
62
71
|
/** More than one `graph` block (fatal). */
|
|
63
|
-
|
|
72
|
+
MULTIPLE_GRAPHS: SECOND_GRAPH_CODE,
|
|
64
73
|
/** A node without an `id`. */
|
|
65
74
|
MISSING_ID: MISSING_ID_CODE,
|
|
66
75
|
/** A node without a `label` under nodeIdFrom "label". */
|
|
@@ -13,9 +13,11 @@ import {
|
|
|
13
13
|
DUPLICATE_EDGE_ID_CODE,
|
|
14
14
|
DUPLICATE_KEY_CODE,
|
|
15
15
|
DUPLICATE_NODE_CODE,
|
|
16
|
+
ENCODING_FALLBACK_CODE,
|
|
16
17
|
HYPEREDGE_CODE,
|
|
17
18
|
ID_MERGED_CODE,
|
|
18
19
|
ID_TEXT_TYPE_CODE,
|
|
20
|
+
INVALID_ENCODING_CODE,
|
|
19
21
|
INVALID_UTF8_CODE,
|
|
20
22
|
MISSING_ENDPOINT_CODE,
|
|
21
23
|
MISSING_ID_CODE,
|
|
@@ -32,6 +34,7 @@ import {
|
|
|
32
34
|
STRAY_TEXT_CODE,
|
|
33
35
|
UNKNOWN_ATTR_TYPE_CODE,
|
|
34
36
|
UNKNOWN_ELEMENT_CODE,
|
|
37
|
+
UNKNOWN_ENCODING_CODE,
|
|
35
38
|
XML_SYNTAX_CODE,
|
|
36
39
|
} from "../../common/codes.js";
|
|
37
40
|
|
|
@@ -114,6 +117,12 @@ export const GRAPHML_ISSUE = Object.freeze({
|
|
|
114
117
|
XML_SYNTAX: XML_SYNTAX_CODE,
|
|
115
118
|
/** Fatal: the input holds invalid UTF-8. */
|
|
116
119
|
INVALID_UTF8: INVALID_UTF8_CODE,
|
|
120
|
+
/** Invalid bytes in the encoding a BOM, a declaration or the encoding option chose (fatal). */
|
|
121
|
+
INVALID_ENCODING: INVALID_ENCODING_CODE,
|
|
122
|
+
/** Bytes that are not UTF-8 and declare no encoding were read as windows-1252. */
|
|
123
|
+
ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
|
|
124
|
+
/** A declared encoding the platform cannot decode was ignored. */
|
|
125
|
+
UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
|
|
117
126
|
/** Fatal: the root element is not `<graphml>`. */
|
|
118
127
|
NOT_GRAPHML: "E_NOT_GRAPHML",
|
|
119
128
|
/** Fatal: the document has no `<graph>`. */
|
|
@@ -51,7 +51,14 @@ import {
|
|
|
51
51
|
} from "../../common/options.js";
|
|
52
52
|
import { ImportReportBuilder } from "../../common/report.js";
|
|
53
53
|
import { isWeightField, parseWeightText } from "../../common/weights.js";
|
|
54
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
isWhitespace,
|
|
56
|
+
localName,
|
|
57
|
+
tokenizeXml,
|
|
58
|
+
xmlDeclaredEncoding,
|
|
59
|
+
type XmlHandler,
|
|
60
|
+
XmlSyntaxError,
|
|
61
|
+
} from "../../common/xml.js";
|
|
55
62
|
import { type CommonImportOptions, type GraphImporter, type ImportInput, type ImportReport } from "../../types.js";
|
|
56
63
|
import {
|
|
57
64
|
EDGE_ID_COLUMN,
|
|
@@ -2005,7 +2012,7 @@ async function importGraphml(
|
|
|
2005
2012
|
reportUnusedOptions(options, report, USED_OPTIONS);
|
|
2006
2013
|
const reader = new GraphmlReader(sink, report, common, yfilesMode);
|
|
2007
2014
|
try {
|
|
2008
|
-
await tokenizeXml(textChunks(input, report, common), reader);
|
|
2015
|
+
await tokenizeXml(textChunks(input, report, { ...common, declaredEncoding: xmlDeclaredEncoding }), reader);
|
|
2009
2016
|
} catch (err) {
|
|
2010
2017
|
if (err instanceof XmlSyntaxError) {
|
|
2011
2018
|
report.fail(GRAPHML_ISSUE.XML_SYNTAX, err.message, { line: err.line });
|
|
@@ -22,7 +22,7 @@ import { type Column, GraphFormatError, type GraphSnapshot, INVALID_INDEX, type
|
|
|
22
22
|
|
|
23
23
|
import { type PairFolding, pairFolding } from "../../common/direction.js";
|
|
24
24
|
import { checkCapabilities, countMixedEdges, LOSS } from "../../common/export.js";
|
|
25
|
-
import { formatF32 } from "../../common/format.js";
|
|
25
|
+
import { formatF32, formatF64 } from "../../common/format.js";
|
|
26
26
|
import { type ResolvedExportOptions, resolveExportOptions } from "../../common/options.js";
|
|
27
27
|
import { explicitWeights } from "../../common/weights.js";
|
|
28
28
|
import { encodeChunks, joinText } from "../../common/writer.js";
|
|
@@ -283,7 +283,8 @@ function numberText(value: number, f32: boolean, nonfinite: Counter): string {
|
|
|
283
283
|
if (f32) {
|
|
284
284
|
return formatF32(value);
|
|
285
285
|
}
|
|
286
|
-
|
|
286
|
+
// -0 is a JSON number too ("-0"); JSON.parse reads it back as -0
|
|
287
|
+
return formatF64(value);
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
/**
|
|
@@ -776,7 +777,7 @@ function planSlot(ctx: PlanContext, column: Column, domain: Domain): ColumnPlan
|
|
|
776
777
|
* What the importer's inference (design section 5.1: JSON numbers are i32 when integral, else
|
|
777
778
|
* f64; strings are strings; arrays and objects are json) changes about a written attribute
|
|
778
779
|
* column beyond what checkCapabilities() already reported for its dtype: an f64 column whose set
|
|
779
|
-
* values are all integers reads back as i32.
|
|
780
|
+
* finite values are all integers reads back as i32 (its non-finite values are written as null).
|
|
780
781
|
* @param ctx - the plan context
|
|
781
782
|
* @param column - the column
|
|
782
783
|
* @param domain - its domain
|
|
@@ -787,11 +788,19 @@ function inferenceNotes(ctx: PlanContext, column: Column, domain: Domain, setRow
|
|
|
787
788
|
return;
|
|
788
789
|
}
|
|
789
790
|
const { data } = column;
|
|
791
|
+
let finite = 0;
|
|
790
792
|
for (let r = 0; r < column.length; r++) {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
+
// a non-finite value is written as null and reads back unset, so it does not keep the column f64
|
|
794
|
+
if (column.isSet(r) && Number.isFinite(data[r])) {
|
|
795
|
+
if (!Number.isInteger(data[r])) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
finite++;
|
|
793
799
|
}
|
|
794
800
|
}
|
|
801
|
+
if (finite === 0) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
795
804
|
ctx.note(
|
|
796
805
|
LOSS.INTEGRAL_F64,
|
|
797
806
|
`${domain} column "${column.meta.name}" is f64 with integral values only; JSON declares no types and it reads back as i32`,
|
|
@@ -40,12 +40,16 @@ import {
|
|
|
40
40
|
DUPLICATE_EDGE_ID_CODE,
|
|
41
41
|
DUPLICATE_NODE_CODE,
|
|
42
42
|
EMPTY_INPUT_CODE,
|
|
43
|
+
ENCODING_FALLBACK_CODE,
|
|
43
44
|
HYPEREDGE_CODE,
|
|
45
|
+
INVALID_ENCODING_CODE,
|
|
46
|
+
INVALID_UTF8_CODE,
|
|
44
47
|
MISSING_ENDPOINT_CODE,
|
|
45
48
|
MISSING_ID_CODE,
|
|
46
49
|
MULTIPLE_GRAPHS_CODE,
|
|
47
50
|
OPTION_IGNORED_CODE,
|
|
48
51
|
SYNTAX_CODE,
|
|
52
|
+
UNKNOWN_ENCODING_CODE,
|
|
49
53
|
UNKNOWN_PARENT_CODE,
|
|
50
54
|
} from "../../common/codes.js";
|
|
51
55
|
import { DirectionResolver, type EdgeKind } from "../../common/direction.js";
|
|
@@ -157,6 +161,14 @@ export const JSON_ISSUE = Object.freeze({
|
|
|
157
161
|
SINK_OPTION: SINK_OPTION_CODE,
|
|
158
162
|
/** A common option the dialect has no use for (nodeIdFrom outside node-link, long, restoreMangledIds). */
|
|
159
163
|
OPTION_IGNORED: OPTION_IGNORED_CODE,
|
|
164
|
+
/** The input holds invalid UTF-8 (fatal). */
|
|
165
|
+
INVALID_UTF8: INVALID_UTF8_CODE,
|
|
166
|
+
/** Invalid bytes in the encoding a BOM, a declaration or the encoding option chose (fatal). */
|
|
167
|
+
INVALID_ENCODING: INVALID_ENCODING_CODE,
|
|
168
|
+
/** Bytes that are not UTF-8 and declare no encoding were read as windows-1252. */
|
|
169
|
+
ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
|
|
170
|
+
/** A declared encoding the platform cannot decode was ignored. */
|
|
171
|
+
UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
|
|
160
172
|
});
|
|
161
173
|
|
|
162
174
|
/** The common options the JSON importer reads (the rest is reported by reportUnusedOptions). */
|
|
@@ -236,6 +248,8 @@ interface ResolvedJsonOptions {
|
|
|
236
248
|
readonly targetKey: string | null;
|
|
237
249
|
readonly indexLinks: boolean | "auto";
|
|
238
250
|
readonly graphIndex: number;
|
|
251
|
+
/** Set by importAll(): every graph of a `graphs` array is read, so none is reported as skipped. */
|
|
252
|
+
readonly all?: boolean;
|
|
239
253
|
}
|
|
240
254
|
|
|
241
255
|
/**
|
|
@@ -1741,11 +1755,11 @@ function jgfGraphOf(ctx: ImportContext, root: JsonRecord): JsonRecord {
|
|
|
1741
1755
|
if (graphs.length === 0) {
|
|
1742
1756
|
report.fail(JSON_ISSUE.SHAPE, "a JGF document needs a graph object or a non-empty graphs array");
|
|
1743
1757
|
}
|
|
1744
|
-
if (graphs.length > 1) {
|
|
1758
|
+
if (graphs.length > 1 && ctx.json.all !== true) {
|
|
1745
1759
|
report.warning(
|
|
1746
1760
|
"unsupported",
|
|
1747
1761
|
JSON_ISSUE.MULTIPLE_GRAPHS,
|
|
1748
|
-
`the document holds ${graphs.length} graphs; only graphs[${ctx.json.graphIndex}] is read`,
|
|
1762
|
+
`the document holds ${graphs.length} graphs; only graphs[${ctx.json.graphIndex}] is read (${graphs.length - 1} skipped), importAll() reads every one`,
|
|
1749
1763
|
{ element: "graphs" },
|
|
1750
1764
|
);
|
|
1751
1765
|
}
|
|
@@ -2232,40 +2246,94 @@ export const jsonImporter: GraphImporter<JsonImportOptions> = Object.freeze({
|
|
|
2232
2246
|
const text = await readText(input, report, resolved);
|
|
2233
2247
|
const root = parseDocument(text, report);
|
|
2234
2248
|
const dialect = detectDialect(root, json.dialect, report);
|
|
2235
|
-
|
|
2236
|
-
reportSinkOptions(sink, options, report);
|
|
2237
|
-
reportUnusedOptions(options, report, USED_OPTIONS);
|
|
2238
|
-
if (dialect === "cytoscape") {
|
|
2239
|
-
importCytoscape(ctx, root);
|
|
2240
|
-
throwIfAborted(resolved.signal);
|
|
2241
|
-
return report.finish();
|
|
2242
|
-
}
|
|
2243
|
-
const doc = isJsonObject(root)
|
|
2244
|
-
? root
|
|
2245
|
-
: report.fail(JSON_ISSUE.SHAPE, `a ${dialect} document must be a JSON object, found ${describe(root)}`);
|
|
2246
|
-
switch (dialect) {
|
|
2247
|
-
case "node-link":
|
|
2248
|
-
case "d3":
|
|
2249
|
-
importNodeLink(ctx, doc, dialect);
|
|
2250
|
-
break;
|
|
2251
|
-
case "jgf":
|
|
2252
|
-
importJgf(ctx, doc);
|
|
2253
|
-
break;
|
|
2254
|
-
case "graphology":
|
|
2255
|
-
importGraphology(ctx, doc);
|
|
2256
|
-
break;
|
|
2257
|
-
case "vis":
|
|
2258
|
-
importVis(ctx, doc);
|
|
2259
|
-
break;
|
|
2260
|
-
default: {
|
|
2261
|
-
const name: string = dialect;
|
|
2262
|
-
throw new GraphFormatError("E_UNSUPPORTED", `unknown dialect ${name}`, {
|
|
2263
|
-
option: "dialect",
|
|
2264
|
-
found: name,
|
|
2265
|
-
});
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
throwIfAborted(resolved.signal);
|
|
2249
|
+
readGraph(root, dialect, sink, report, resolved, json, options);
|
|
2269
2250
|
return report.finish();
|
|
2270
2251
|
},
|
|
2252
|
+
|
|
2253
|
+
/**
|
|
2254
|
+
* Read every graph of a JSON document: each entry of a JGF `graphs` array into its own sink;
|
|
2255
|
+
* any other document holds one graph.
|
|
2256
|
+
* @param input - the text, bytes or stream
|
|
2257
|
+
* @param sinkFor - the sink of the graph with this index, called before its first push
|
|
2258
|
+
* @param options - format-specific and common options
|
|
2259
|
+
* @returns one report per graph
|
|
2260
|
+
*/
|
|
2261
|
+
async importAll(
|
|
2262
|
+
input: ImportInput,
|
|
2263
|
+
sinkFor: (index: number) => GraphSink,
|
|
2264
|
+
options?: JsonImportOptions & CommonImportOptions,
|
|
2265
|
+
): Promise<ImportReport[]> {
|
|
2266
|
+
const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
|
|
2267
|
+
const json = resolveJsonOptions(options);
|
|
2268
|
+
const first = new ImportReportBuilder("json", resolved.errorLimit);
|
|
2269
|
+
const text = await readText(input, first, resolved);
|
|
2270
|
+
const root = parseDocument(text, first);
|
|
2271
|
+
const dialect = detectDialect(root, json.dialect, first);
|
|
2272
|
+
const graphs =
|
|
2273
|
+
dialect === "jgf" && isJsonObject(root) && !isJsonObject(root.graph) && Array.isArray(root.graphs)
|
|
2274
|
+
? root.graphs.length
|
|
2275
|
+
: 1;
|
|
2276
|
+
const reports: ImportReport[] = [];
|
|
2277
|
+
for (let i = 0; i < Math.max(graphs, 1); i++) {
|
|
2278
|
+
const report = i === 0 ? first : new ImportReportBuilder("json", resolved.errorLimit);
|
|
2279
|
+
readGraph(root, dialect, sinkFor(i), report, resolved, { ...json, graphIndex: i, all: true }, options);
|
|
2280
|
+
reports.push(report.finish());
|
|
2281
|
+
}
|
|
2282
|
+
return reports;
|
|
2283
|
+
},
|
|
2271
2284
|
});
|
|
2285
|
+
|
|
2286
|
+
/**
|
|
2287
|
+
* Read one graph of a parsed document into a sink.
|
|
2288
|
+
* @param root - the parsed document
|
|
2289
|
+
* @param dialect - its dialect
|
|
2290
|
+
* @param sink - the sink
|
|
2291
|
+
* @param report - the graph's report
|
|
2292
|
+
* @param resolved - the resolved common options
|
|
2293
|
+
* @param json - the resolved JSON options (graphIndex picks the JGF graph)
|
|
2294
|
+
* @param options - the caller's options, for the sink and unused-option checks
|
|
2295
|
+
*/
|
|
2296
|
+
function readGraph(
|
|
2297
|
+
root: unknown,
|
|
2298
|
+
dialect: JsonDialect,
|
|
2299
|
+
sink: GraphSink,
|
|
2300
|
+
report: ImportReportBuilder,
|
|
2301
|
+
resolved: ResolvedImportOptions,
|
|
2302
|
+
json: ResolvedJsonOptions,
|
|
2303
|
+
options: (JsonImportOptions & CommonImportOptions) | undefined,
|
|
2304
|
+
): void {
|
|
2305
|
+
const ctx = new ImportContext(sink, report, resolved, json, options?.defaultDirected !== undefined);
|
|
2306
|
+
reportSinkOptions(sink, options, report);
|
|
2307
|
+
reportUnusedOptions(options, report, USED_OPTIONS);
|
|
2308
|
+
if (dialect === "cytoscape") {
|
|
2309
|
+
importCytoscape(ctx, root);
|
|
2310
|
+
throwIfAborted(resolved.signal);
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
const doc = isJsonObject(root)
|
|
2314
|
+
? root
|
|
2315
|
+
: report.fail(JSON_ISSUE.SHAPE, `a ${dialect} document must be a JSON object, found ${describe(root)}`);
|
|
2316
|
+
switch (dialect) {
|
|
2317
|
+
case "node-link":
|
|
2318
|
+
case "d3":
|
|
2319
|
+
importNodeLink(ctx, doc, dialect);
|
|
2320
|
+
break;
|
|
2321
|
+
case "jgf":
|
|
2322
|
+
importJgf(ctx, doc);
|
|
2323
|
+
break;
|
|
2324
|
+
case "graphology":
|
|
2325
|
+
importGraphology(ctx, doc);
|
|
2326
|
+
break;
|
|
2327
|
+
case "vis":
|
|
2328
|
+
importVis(ctx, doc);
|
|
2329
|
+
break;
|
|
2330
|
+
default: {
|
|
2331
|
+
const name: string = dialect;
|
|
2332
|
+
throw new GraphFormatError("E_UNSUPPORTED", `unknown dialect ${name}`, {
|
|
2333
|
+
option: "dialect",
|
|
2334
|
+
found: name,
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
throwIfAborted(resolved.signal);
|
|
2339
|
+
}
|
|
@@ -467,7 +467,7 @@ class Neo4jImportSession {
|
|
|
467
467
|
async run(inputs: readonly ImportInput[]): Promise<void> {
|
|
468
468
|
const progress = new ProgressTracker(this.common.onProgress, inputs);
|
|
469
469
|
for (const input of inputs) {
|
|
470
|
-
await this.readInput(input, progress.optionsFor(this.common.signal));
|
|
470
|
+
await this.readInput(input, { ...progress.optionsFor(this.common.signal), encoding: this.common.encoding });
|
|
471
471
|
progress.finishInput();
|
|
472
472
|
}
|
|
473
473
|
if (this.ignoredColumns > 0) {
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
COLUMN_RENAMED_CODE,
|
|
9
|
+
ENCODING_FALLBACK_CODE,
|
|
10
|
+
INVALID_ENCODING_CODE,
|
|
9
11
|
INVALID_UTF8_CODE,
|
|
10
12
|
MUTUAL_EXPANDED_CODE,
|
|
11
13
|
OPTION_IGNORED_CODE,
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
ROLE_DROPPED_CODE,
|
|
14
16
|
SINK_OPTION_CODE,
|
|
15
17
|
UNKNOWN_ATTR_TYPE_CODE,
|
|
18
|
+
UNKNOWN_ENCODING_CODE,
|
|
16
19
|
WEIGHT_KEY_CLASH_CODE,
|
|
17
20
|
} from "../../common/codes.js";
|
|
18
21
|
import { BAD_QUOTE_CODE, UNCLOSED_QUOTE_CODE } from "../csv/records.js";
|
|
@@ -50,6 +53,12 @@ export { ID_SPACE_COLUMN, LABELS_COLUMN, neo4jImporter, type Neo4jImportOptions,
|
|
|
50
53
|
export const NEO4J_ISSUE = Object.freeze({
|
|
51
54
|
/** The input holds invalid UTF-8 (fatal). */
|
|
52
55
|
INVALID_UTF8: INVALID_UTF8_CODE,
|
|
56
|
+
/** Invalid bytes in the encoding a BOM, a declaration or the encoding option chose (fatal). */
|
|
57
|
+
INVALID_ENCODING: INVALID_ENCODING_CODE,
|
|
58
|
+
/** Bytes that are not UTF-8 and declare no encoding were read as windows-1252. */
|
|
59
|
+
ENCODING_FALLBACK: ENCODING_FALLBACK_CODE,
|
|
60
|
+
/** A declared encoding the platform cannot decode was ignored. */
|
|
61
|
+
UNKNOWN_ENCODING: UNKNOWN_ENCODING_CODE,
|
|
53
62
|
/** An unterminated quoted field (fatal). */
|
|
54
63
|
CSV_UNCLOSED_QUOTE: UNCLOSED_QUOTE_CODE,
|
|
55
64
|
/** Text after a closing quote (fatal). */
|
|
@@ -20,6 +20,7 @@ import { capabilities, checkCapabilities, LOSS, type SanitizedIds, sanitizeIds }
|
|
|
20
20
|
import { formatDecimal, formatF32, formatF64, formatInteger } from "../../common/format.js";
|
|
21
21
|
import { canonicalId } from "../../common/ids.js";
|
|
22
22
|
import { resolveExportOptions } from "../../common/options.js";
|
|
23
|
+
import { inferTextDtype } from "../../common/text.js";
|
|
23
24
|
import { explicitWeights } from "../../common/weights.js";
|
|
24
25
|
import { encodeChunks, joinText } from "../../common/writer.js";
|
|
25
26
|
import { type CommonExportOptions, type ExportCapabilities, type GraphExporter, type LossNote } from "../../types.js";
|
|
@@ -74,6 +75,8 @@ export const PAJEK_LOSS = Object.freeze({
|
|
|
74
75
|
ROLE_ASSUMED: LOSS.ROLE_ASSUMED,
|
|
75
76
|
/** Under sanitizeIds "mangle": an original id whose text reads back as the other type under ids "canonical". */
|
|
76
77
|
ID_TEXT_TYPE: LOSS.ID_TEXT_TYPE,
|
|
78
|
+
/** Parameter text that looks like a number or a boolean reads back as one (parameters are untyped, design 5.1). */
|
|
79
|
+
TEXT_INFERRED: LOSS.TEXT_INFERRED,
|
|
77
80
|
});
|
|
78
81
|
|
|
79
82
|
/** The roles Pajek has a slot for beyond the structural, position and temporal ones. */
|
|
@@ -400,7 +403,7 @@ function cellText(column: Column, row: number): string {
|
|
|
400
403
|
|
|
401
404
|
/**
|
|
402
405
|
* Count the cells of a plan that cannot be written (text with a quote or line break, non-finite
|
|
403
|
-
* f64) and add the notes.
|
|
406
|
+
* f64) or read back typed (parameter text that looks like a number or a boolean) and add the notes.
|
|
404
407
|
* @param planned - the plan
|
|
405
408
|
* @param domain - node or edge
|
|
406
409
|
* @param notes - the note list
|
|
@@ -413,6 +416,7 @@ function checkCells(planned: readonly PlannedColumn[], domain: "node" | "edge",
|
|
|
413
416
|
const { name } = column.meta;
|
|
414
417
|
let badText = 0;
|
|
415
418
|
let nonFinite = 0;
|
|
419
|
+
let typed = 0;
|
|
416
420
|
for (let r = 0; r < column.length; r++) {
|
|
417
421
|
if (!column.isSet(r)) {
|
|
418
422
|
continue;
|
|
@@ -427,8 +431,11 @@ function checkCells(planned: readonly PlannedColumn[], domain: "node" | "edge",
|
|
|
427
431
|
nonFinite++;
|
|
428
432
|
}
|
|
429
433
|
} else if (column.dtype === "string" || column.dtype === "dict") {
|
|
430
|
-
|
|
434
|
+
const text = column.value(r) as string;
|
|
435
|
+
if (!isPajekLabel(text)) {
|
|
431
436
|
badText++;
|
|
437
|
+
} else if (slot === "param" && inferTextDtype(text) !== "string") {
|
|
438
|
+
typed++;
|
|
432
439
|
}
|
|
433
440
|
}
|
|
434
441
|
}
|
|
@@ -442,6 +449,16 @@ function checkCells(planned: readonly PlannedColumn[], domain: "node" | "edge",
|
|
|
442
449
|
),
|
|
443
450
|
);
|
|
444
451
|
}
|
|
452
|
+
if (typed > 0) {
|
|
453
|
+
notes.push(
|
|
454
|
+
note(
|
|
455
|
+
PAJEK_LOSS.TEXT_INFERRED,
|
|
456
|
+
`${typed} value(s) of ${domain} column "${name}" look like numbers or booleans; Pajek parameters are untyped and they read back as such`,
|
|
457
|
+
name,
|
|
458
|
+
typed,
|
|
459
|
+
),
|
|
460
|
+
);
|
|
461
|
+
}
|
|
445
462
|
if (nonFinite > 0) {
|
|
446
463
|
notes.push(
|
|
447
464
|
note(
|