@graphty/graph-io 0.3.18 → 0.3.20
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 +80 -2
- package/dist/chunks/{escape-D-gZWO26.js → escape-B-tkk_Cl.js} +74 -16
- package/dist/chunks/escape-B-tkk_Cl.js.map +1 -0
- package/dist/chunks/{writer-GAdltGmC.js → export-Bh60Dv-n.js} +7 -252
- package/dist/chunks/export-Bh60Dv-n.js.map +1 -0
- package/dist/chunks/exporter-BGrsWamJ.js +736 -0
- package/dist/chunks/exporter-BGrsWamJ.js.map +1 -0
- package/dist/chunks/exporter-DIeGJXAZ.js +834 -0
- package/dist/chunks/exporter-DIeGJXAZ.js.map +1 -0
- package/dist/chunks/{importer-Br_QeAeE.js → importer-BWY2FFc5.js} +28 -6
- package/dist/chunks/importer-BWY2FFc5.js.map +1 -0
- package/dist/chunks/importer-BaQpCEcJ.js +2194 -0
- package/dist/chunks/importer-BaQpCEcJ.js.map +1 -0
- package/dist/chunks/importer-DD-xv9_Y.js +1471 -0
- package/dist/chunks/importer-DD-xv9_Y.js.map +1 -0
- package/dist/chunks/{importer-aNJfe0qu.js → importer-DEcKhsmQ.js} +2 -2
- package/dist/chunks/{importer-aNJfe0qu.js.map → importer-DEcKhsmQ.js.map} +1 -1
- package/dist/chunks/{importer-DHagxvDD.js → importer-DIrbbnAf.js} +6 -4
- package/dist/chunks/{importer-DHagxvDD.js.map → importer-DIrbbnAf.js.map} +1 -1
- package/dist/chunks/importer-D_e7e7LX.js +3055 -0
- package/dist/chunks/importer-D_e7e7LX.js.map +1 -0
- package/dist/chunks/{importer-Du5crN9l.js → importer-Wv4C4gaF.js} +278 -82
- package/dist/chunks/importer-Wv4C4gaF.js.map +1 -0
- package/dist/chunks/importer-vi3bSdtw.js +1713 -0
- package/dist/chunks/importer-vi3bSdtw.js.map +1 -0
- package/dist/chunks/{importer-d0uQxFp6.js → importer-zGLo8Dg_.js} +6 -4
- package/dist/chunks/{importer-d0uQxFp6.js.map → importer-zGLo8Dg_.js.map} +1 -1
- package/dist/chunks/json-elements-DDS8N2Dd.js +779 -0
- package/dist/chunks/json-elements-DDS8N2Dd.js.map +1 -0
- package/dist/chunks/{records-Bk9jgodz.js → records-dbRkxwaq.js} +2 -2
- package/dist/chunks/{records-Bk9jgodz.js.map → records-dbRkxwaq.js.map} +1 -1
- package/dist/chunks/{report-BOk0p5y8.js → report-B1z4WT9e.js} +143 -111
- package/dist/chunks/{report-BOk0p5y8.js.map → report-B1z4WT9e.js.map} +1 -1
- package/dist/chunks/weights-Dzba96G3.js +176 -0
- package/dist/chunks/weights-Dzba96G3.js.map +1 -0
- package/dist/chunks/writer-C7flM-Ih.js +77 -0
- package/dist/chunks/writer-C7flM-Ih.js.map +1 -0
- package/dist/csv.js +6 -4
- package/dist/csv.js.map +1 -1
- package/dist/cx.d.ts +1 -0
- package/dist/cx.js +6 -0
- package/dist/cx.js.map +1 -0
- package/dist/cx2.d.ts +1 -0
- package/dist/cx2.js +10 -0
- package/dist/cx2.js.map +1 -0
- package/dist/cys.d.ts +1 -0
- package/dist/cys.js +6 -0
- package/dist/cys.js.map +1 -0
- package/dist/dot.js +1 -1
- package/dist/gexf.js +16 -4
- package/dist/gexf.js.map +1 -1
- package/dist/gml.js +13 -14
- package/dist/gml.js.map +1 -1
- package/dist/graph-io.js +302 -399
- package/dist/graph-io.js.map +1 -1
- package/dist/graphml.js +1 -1
- package/dist/json.js +1 -1
- package/dist/neo4j.js +6 -4
- package/dist/neo4j.js.map +1 -1
- package/dist/obo.js +1 -1
- package/dist/pajek.js +1 -1
- package/dist/src/common/cell-budget.d.ts +84 -0
- package/dist/src/common/cell-budget.d.ts.map +1 -0
- package/dist/src/common/cell-budget.js +138 -0
- package/dist/src/common/cell-budget.js.map +1 -0
- package/dist/src/common/input.d.ts +10 -0
- package/dist/src/common/input.d.ts.map +1 -1
- package/dist/src/common/input.js +39 -0
- package/dist/src/common/input.js.map +1 -1
- package/dist/src/common/json-elements.d.ts +286 -0
- package/dist/src/common/json-elements.d.ts.map +1 -0
- package/dist/src/common/json-elements.js +926 -0
- package/dist/src/common/json-elements.js.map +1 -0
- package/dist/src/common/options.d.ts +6 -0
- package/dist/src/common/options.d.ts.map +1 -1
- package/dist/src/common/options.js +1 -1
- package/dist/src/common/options.js.map +1 -1
- package/dist/src/common/xml.d.ts +37 -3
- package/dist/src/common/xml.d.ts.map +1 -1
- package/dist/src/common/xml.js +86 -6
- package/dist/src/common/xml.js.map +1 -1
- package/dist/src/common/zip.d.ts +92 -0
- package/dist/src/common/zip.d.ts.map +1 -0
- package/dist/src/common/zip.js +399 -0
- package/dist/src/common/zip.js.map +1 -0
- package/dist/src/formats/cx/importer.d.ts +133 -0
- package/dist/src/formats/cx/importer.d.ts.map +1 -0
- package/dist/src/formats/cx/importer.js +2220 -0
- package/dist/src/formats/cx/importer.js.map +1 -0
- package/dist/src/formats/cx/index.d.ts +7 -0
- package/dist/src/formats/cx/index.d.ts.map +1 -0
- package/dist/src/formats/cx/index.js +7 -0
- package/dist/src/formats/cx/index.js.map +1 -0
- package/dist/src/formats/cx2/exporter.d.ts +59 -0
- package/dist/src/formats/cx2/exporter.d.ts.map +1 -0
- package/dist/src/formats/cx2/exporter.js +864 -0
- package/dist/src/formats/cx2/exporter.js.map +1 -0
- package/dist/src/formats/cx2/importer.d.ts +169 -0
- package/dist/src/formats/cx2/importer.d.ts.map +1 -0
- package/dist/src/formats/cx2/importer.js +1652 -0
- package/dist/src/formats/cx2/importer.js.map +1 -0
- package/dist/src/formats/cx2/index.d.ts +7 -0
- package/dist/src/formats/cx2/index.d.ts.map +1 -0
- package/dist/src/formats/cx2/index.js +7 -0
- package/dist/src/formats/cx2/index.js.map +1 -0
- package/dist/src/formats/cys/constants.d.ts +68 -0
- package/dist/src/formats/cys/constants.d.ts.map +1 -0
- package/dist/src/formats/cys/constants.js +76 -0
- package/dist/src/formats/cys/constants.js.map +1 -0
- package/dist/src/formats/cys/importer.d.ts +38 -0
- package/dist/src/formats/cys/importer.d.ts.map +1 -0
- package/dist/src/formats/cys/importer.js +830 -0
- package/dist/src/formats/cys/importer.js.map +1 -0
- package/dist/src/formats/cys/index.d.ts +7 -0
- package/dist/src/formats/cys/index.d.ts.map +1 -0
- package/dist/src/formats/cys/index.js +7 -0
- package/dist/src/formats/cys/index.js.map +1 -0
- package/dist/src/formats/cys/session.d.ts +132 -0
- package/dist/src/formats/cys/session.d.ts.map +1 -0
- package/dist/src/formats/cys/session.js +315 -0
- package/dist/src/formats/cys/session.js.map +1 -0
- package/dist/src/formats/cys/tables.d.ts +90 -0
- package/dist/src/formats/cys/tables.d.ts.map +1 -0
- package/dist/src/formats/cys/tables.js +293 -0
- package/dist/src/formats/cys/tables.js.map +1 -0
- package/dist/src/formats/dot/exporter.d.ts +2 -0
- package/dist/src/formats/dot/exporter.d.ts.map +1 -1
- package/dist/src/formats/dot/exporter.js +12 -0
- package/dist/src/formats/dot/exporter.js.map +1 -1
- package/dist/src/formats/dot/importer.js +6 -2
- package/dist/src/formats/dot/importer.js.map +1 -1
- package/dist/src/formats/gexf/exporter.d.ts +2 -0
- package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
- package/dist/src/formats/gexf/exporter.js +6 -0
- package/dist/src/formats/gexf/exporter.js.map +1 -1
- package/dist/src/formats/gexf/importer.d.ts +0 -2
- package/dist/src/formats/gexf/importer.d.ts.map +1 -1
- package/dist/src/formats/gexf/importer.js +1 -3
- package/dist/src/formats/gexf/importer.js.map +1 -1
- package/dist/src/formats/gexf/index.d.ts +1 -1
- package/dist/src/formats/gexf/index.js +2 -2
- package/dist/src/formats/gexf/index.js.map +1 -1
- package/dist/src/formats/gml/exporter.d.ts +0 -8
- package/dist/src/formats/gml/exporter.d.ts.map +1 -1
- package/dist/src/formats/gml/exporter.js +8 -18
- package/dist/src/formats/gml/exporter.js.map +1 -1
- package/dist/src/formats/json/importer.d.ts.map +1 -1
- package/dist/src/formats/json/importer.js +6 -104
- package/dist/src/formats/json/importer.js.map +1 -1
- package/dist/src/formats/xgmml/columns.d.ts +152 -0
- package/dist/src/formats/xgmml/columns.d.ts.map +1 -0
- package/dist/src/formats/xgmml/columns.js +593 -0
- package/dist/src/formats/xgmml/columns.js.map +1 -0
- package/dist/src/formats/xgmml/constants.d.ts +196 -0
- package/dist/src/formats/xgmml/constants.d.ts.map +1 -0
- package/dist/src/formats/xgmml/constants.js +197 -0
- package/dist/src/formats/xgmml/constants.js.map +1 -0
- package/dist/src/formats/xgmml/document.d.ts +361 -0
- package/dist/src/formats/xgmml/document.d.ts.map +1 -0
- package/dist/src/formats/xgmml/document.js +695 -0
- package/dist/src/formats/xgmml/document.js.map +1 -0
- package/dist/src/formats/xgmml/emit.d.ts +341 -0
- package/dist/src/formats/xgmml/emit.d.ts.map +1 -0
- package/dist/src/formats/xgmml/emit.js +1275 -0
- package/dist/src/formats/xgmml/emit.js.map +1 -0
- package/dist/src/formats/xgmml/exporter.d.ts +24 -0
- package/dist/src/formats/xgmml/exporter.d.ts.map +1 -0
- package/dist/src/formats/xgmml/exporter.js +999 -0
- package/dist/src/formats/xgmml/exporter.js.map +1 -0
- package/dist/src/formats/xgmml/importer.d.ts +58 -0
- package/dist/src/formats/xgmml/importer.d.ts.map +1 -0
- package/dist/src/formats/xgmml/importer.js +360 -0
- package/dist/src/formats/xgmml/importer.js.map +1 -0
- package/dist/src/formats/xgmml/index.d.ts +8 -0
- package/dist/src/formats/xgmml/index.d.ts.map +1 -0
- package/dist/src/formats/xgmml/index.js +8 -0
- package/dist/src/formats/xgmml/index.js.map +1 -0
- package/dist/src/formats/xgmml/values.d.ts +60 -0
- package/dist/src/formats/xgmml/values.d.ts.map +1 -0
- package/dist/src/formats/xgmml/values.js +148 -0
- package/dist/src/formats/xgmml/values.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/registry.d.ts +7 -0
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +30 -10
- 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 +4 -0
- package/dist/src/sniff.js.map +1 -1
- package/dist/xgmml.d.ts +1 -0
- package/dist/xgmml.js +9 -0
- package/dist/xgmml.js.map +1 -0
- package/package.json +25 -2
- package/src/common/cell-budget.ts +170 -0
- package/src/common/input.ts +40 -0
- package/src/common/json-elements.ts +1147 -0
- package/src/common/options.ts +1 -1
- package/src/common/xml.ts +127 -6
- package/src/common/zip.ts +472 -0
- package/src/formats/cx/importer.ts +2733 -0
- package/src/formats/cx/index.ts +7 -0
- package/src/formats/cx2/exporter.ts +1036 -0
- package/src/formats/cx2/importer.ts +2187 -0
- package/src/formats/cx2/index.ts +7 -0
- package/src/formats/cys/constants.ts +99 -0
- package/src/formats/cys/importer.ts +1100 -0
- package/src/formats/cys/index.ts +7 -0
- package/src/formats/cys/session.ts +428 -0
- package/src/formats/cys/tables.ts +400 -0
- package/src/formats/dot/exporter.ts +17 -0
- package/src/formats/dot/importer.ts +6 -2
- package/src/formats/gexf/exporter.ts +11 -0
- package/src/formats/gexf/importer.ts +1 -2
- package/src/formats/gexf/index.ts +1 -1
- package/src/formats/gml/exporter.ts +8 -19
- package/src/formats/json/importer.ts +6 -105
- package/src/formats/xgmml/columns.ts +747 -0
- package/src/formats/xgmml/constants.ts +265 -0
- package/src/formats/xgmml/document.ts +975 -0
- package/src/formats/xgmml/emit.ts +1563 -0
- package/src/formats/xgmml/exporter.ts +1215 -0
- package/src/formats/xgmml/importer.ts +491 -0
- package/src/formats/xgmml/index.ts +8 -0
- package/src/formats/xgmml/values.ts +183 -0
- package/src/index.ts +19 -0
- package/src/registry.ts +46 -15
- package/src/sniff.ts +18 -1
- package/dist/chunks/escape-D-gZWO26.js.map +0 -1
- package/dist/chunks/importer-Br_QeAeE.js.map +0 -1
- package/dist/chunks/importer-Du5crN9l.js.map +0 -1
- package/dist/chunks/writer-GAdltGmC.js.map +0 -1
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The XGMML document model: one pass of the shared XML tokenizer turns an XGMML document into
|
|
3
|
+
* plain records (graphs, nodes, edges, atts, graphics) that keep everything the file says, with
|
|
4
|
+
* forward references (`xlink:href`, an edge before its nodes) left as ids. The importer
|
|
5
|
+
* (importer.ts) resolves the records into a sink once the outermost `</graph>` has closed, as
|
|
6
|
+
* Cytoscape does, and the `.cys` importer reuses the same records for the network and view files
|
|
7
|
+
* of a session.
|
|
8
|
+
*
|
|
9
|
+
* It recognises the five shapes of `research-xgmml.md` section 2: the 1.0 draft, the Cytoscape
|
|
10
|
+
* 2.x export and session, the 3.x export, the 3.x session network file (root `cy:registered="0"`)
|
|
11
|
+
* and the 3.x session view file (root `cy:view="1"`).
|
|
12
|
+
*/
|
|
13
|
+
import { escapeXmlAttribute, escapeXmlText } from "../../common/escape.js";
|
|
14
|
+
import { isWhitespace, localName } from "../../common/xml.js";
|
|
15
|
+
import { XGMML_ISSUE, XGMML_NAMESPACE } from "./constants.js";
|
|
16
|
+
/** The XML attributes the parser reads on `<node>`. */
|
|
17
|
+
const NODE_READ = new Set(["id", "label", "name", "xlink:href", "cy:nodeId"]);
|
|
18
|
+
/** The XML attributes the parser reads on `<edge>`. */
|
|
19
|
+
const EDGE_READ = new Set([
|
|
20
|
+
"id",
|
|
21
|
+
"label",
|
|
22
|
+
"name",
|
|
23
|
+
"source",
|
|
24
|
+
"target",
|
|
25
|
+
"weight",
|
|
26
|
+
"cy:directed",
|
|
27
|
+
"xlink:href",
|
|
28
|
+
"cy:edgeId",
|
|
29
|
+
]);
|
|
30
|
+
/** The XML attributes an `<att>` uses as its own; any other is kept in `extra`. */
|
|
31
|
+
const ATT_READ = new Set([
|
|
32
|
+
"name",
|
|
33
|
+
"label",
|
|
34
|
+
"type",
|
|
35
|
+
"value",
|
|
36
|
+
"cy:type",
|
|
37
|
+
"cy:elementType",
|
|
38
|
+
"cy:hidden",
|
|
39
|
+
"cy:equation",
|
|
40
|
+
"cy:editable",
|
|
41
|
+
]);
|
|
42
|
+
/** Cytoscape's spellings of true (`1`, `true`, `yes`, any case). */
|
|
43
|
+
const TRUE_TEXT = /^(1|true|yes)$/i;
|
|
44
|
+
/**
|
|
45
|
+
* Whether a Cytoscape flag text is true.
|
|
46
|
+
* @param text - the text, or undefined
|
|
47
|
+
* @returns true for 1, true or yes in any case
|
|
48
|
+
*/
|
|
49
|
+
export function isCyTrue(text) {
|
|
50
|
+
return text !== null && text !== undefined && TRUE_TEXT.test(text.trim());
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The name an XML attribute is known by, its `xlink` prefix normalised whatever prefix the file
|
|
54
|
+
* binds XLink to (galFiltered.xgmml uses `ns1`).
|
|
55
|
+
* @param name - the attribute name as written
|
|
56
|
+
* @param xlinkPrefixes - the prefixes bound to the XLink namespace
|
|
57
|
+
* @returns the name with `xlink:` for XLink attributes
|
|
58
|
+
*/
|
|
59
|
+
function normaliseName(name, xlinkPrefixes) {
|
|
60
|
+
const colon = name.indexOf(":");
|
|
61
|
+
if (colon > 0 && xlinkPrefixes.has(name.slice(0, colon))) {
|
|
62
|
+
return `xlink:${name.slice(colon + 1)}`;
|
|
63
|
+
}
|
|
64
|
+
return name;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The tokenizer handler that builds an XgmmlDocument.
|
|
68
|
+
*/
|
|
69
|
+
export class XgmmlParser {
|
|
70
|
+
/**
|
|
71
|
+
* Create a parser.
|
|
72
|
+
* @param report - the report issues are recorded in
|
|
73
|
+
*/
|
|
74
|
+
constructor(report) {
|
|
75
|
+
this.frames = [];
|
|
76
|
+
this.xlinkPrefixes = new Set(["xlink"]);
|
|
77
|
+
this.rootGraph = null;
|
|
78
|
+
this.nodes = [];
|
|
79
|
+
this.edges = [];
|
|
80
|
+
this.graphs = [];
|
|
81
|
+
this.cytoscape = false;
|
|
82
|
+
this.xgmmlNamespace = false;
|
|
83
|
+
/** The prefix of the root element (`xgmml` in `<xgmml:graph>`), or null when it has none. */
|
|
84
|
+
this.rootPrefix = null;
|
|
85
|
+
this.rdf = {};
|
|
86
|
+
this.rdfXml = null;
|
|
87
|
+
/** The local name of the RDF element whose text is being read, or null. */
|
|
88
|
+
this.rdfElement = null;
|
|
89
|
+
this.report = report;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The parsed document.
|
|
93
|
+
* @returns the document; E_NO_GRAPH (fatal) when no root graph was read
|
|
94
|
+
*/
|
|
95
|
+
document() {
|
|
96
|
+
const root = this.rootGraph;
|
|
97
|
+
if (root === null) {
|
|
98
|
+
return this.report.fail(XGMML_ISSUE.NO_GRAPH, "the document has no <graph> element");
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
root,
|
|
102
|
+
nodes: this.nodes,
|
|
103
|
+
edges: this.edges,
|
|
104
|
+
graphs: this.graphs,
|
|
105
|
+
cytoscape: this.cytoscape,
|
|
106
|
+
xgmmlNamespace: this.xgmmlNamespace,
|
|
107
|
+
rdf: this.rdf,
|
|
108
|
+
rdfXml: this.rdfXml,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* An element starts.
|
|
113
|
+
* @param rawName - the element name as written
|
|
114
|
+
* @param rawAttrs - its attributes
|
|
115
|
+
* @param line - the line
|
|
116
|
+
*/
|
|
117
|
+
start(rawName, rawAttrs, line) {
|
|
118
|
+
const top = this.frames.length === 0 ? null : this.frames[this.frames.length - 1];
|
|
119
|
+
if (top === null) {
|
|
120
|
+
this.startRoot(rawName, rawAttrs, line);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (top.kind === "skip") {
|
|
124
|
+
this.frames.push(top);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (top.kind === "capture") {
|
|
128
|
+
this.capture(top, rawName, rawAttrs);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const attrs = this.normalise(rawAttrs);
|
|
132
|
+
const name = localName(rawName);
|
|
133
|
+
switch (top.kind) {
|
|
134
|
+
case "graph":
|
|
135
|
+
this.startInGraph(top.graph, rawName, name, attrs, line);
|
|
136
|
+
return;
|
|
137
|
+
case "node":
|
|
138
|
+
this.startInElement(top.node, rawName, name, attrs, line);
|
|
139
|
+
return;
|
|
140
|
+
case "edge":
|
|
141
|
+
this.startInElement(top.edge, rawName, name, attrs, line);
|
|
142
|
+
return;
|
|
143
|
+
case "att":
|
|
144
|
+
this.startInAtt(top, rawName, name, attrs, line);
|
|
145
|
+
return;
|
|
146
|
+
case "graphics":
|
|
147
|
+
this.startInGraphics(top.target, rawName, name, attrs, line);
|
|
148
|
+
return;
|
|
149
|
+
case "line":
|
|
150
|
+
if (name === "point") {
|
|
151
|
+
top.points.push(Object.fromEntries(attrs));
|
|
152
|
+
this.frames.push({ kind: "skip" });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
this.unknownElement(rawName, line);
|
|
156
|
+
return;
|
|
157
|
+
default: {
|
|
158
|
+
const kind = top;
|
|
159
|
+
throw new Error(`unknown XGMML frame ${String(kind)}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* An element ends.
|
|
165
|
+
* @param name - the element name
|
|
166
|
+
* @param _line - the line
|
|
167
|
+
*/
|
|
168
|
+
end(name, _line) {
|
|
169
|
+
const frame = this.frames.pop();
|
|
170
|
+
const below = this.frames.length === 0 ? null : this.frames[this.frames.length - 1];
|
|
171
|
+
switch (frame?.kind) {
|
|
172
|
+
case "capture":
|
|
173
|
+
frame.att.xml = `${frame.att.xml ?? ""}</${name}>`;
|
|
174
|
+
frame.depth--;
|
|
175
|
+
if (frame.depth > 0) {
|
|
176
|
+
this.frames.push(frame);
|
|
177
|
+
}
|
|
178
|
+
else if (frame.att.name === "networkMetadata") {
|
|
179
|
+
this.rdfXml = frame.att.xml;
|
|
180
|
+
}
|
|
181
|
+
this.rdfElement = null;
|
|
182
|
+
return;
|
|
183
|
+
case "att":
|
|
184
|
+
if (below?.kind === "graphics") {
|
|
185
|
+
this.graphicsAtt(below.target, frame.att);
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
case "line":
|
|
189
|
+
if (below?.kind === "graphics") {
|
|
190
|
+
below.target.graphics = { ...(below.target.graphics ?? {}), Line: frame.points };
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
default:
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Character data.
|
|
198
|
+
* @param text - the text
|
|
199
|
+
* @param line - the line
|
|
200
|
+
*/
|
|
201
|
+
text(text, line) {
|
|
202
|
+
const top = this.frames.length === 0 ? null : this.frames[this.frames.length - 1];
|
|
203
|
+
if (top === null || top.kind === "skip") {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (top.kind === "capture") {
|
|
207
|
+
top.att.xml = `${top.att.xml ?? ""}${escapeXmlText(text)}`;
|
|
208
|
+
this.captureRdfText(text);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (isWhitespace(text)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (top.kind === "att") {
|
|
215
|
+
top.att.text += text;
|
|
216
|
+
}
|
|
217
|
+
this.report.warnOnce("validation-error", XGMML_ISSUE.STRAY_TEXT, top.kind === "att"
|
|
218
|
+
? "text inside <att> is ignored; an XGMML value is the value attribute"
|
|
219
|
+
: "text where XGMML allows only elements was ignored", { line });
|
|
220
|
+
}
|
|
221
|
+
// ------------------------------------------------------------------ structure
|
|
222
|
+
/**
|
|
223
|
+
* The XML attribute map with every XLink prefix written `xlink:`.
|
|
224
|
+
* @param attrs - the attributes as written
|
|
225
|
+
* @returns the normalised map
|
|
226
|
+
*/
|
|
227
|
+
normalise(attrs) {
|
|
228
|
+
const out = new Map();
|
|
229
|
+
for (const [key, value] of attrs) {
|
|
230
|
+
out.set(normaliseName(key, this.xlinkPrefixes), value);
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The root element: a `<graph>`, else the document is not XGMML.
|
|
236
|
+
* @param rawName - the name as written
|
|
237
|
+
* @param rawAttrs - its attributes
|
|
238
|
+
* @param line - the line
|
|
239
|
+
*/
|
|
240
|
+
startRoot(rawName, rawAttrs, line) {
|
|
241
|
+
const local = localName(rawName);
|
|
242
|
+
const prefix = rawName.includes(":") ? rawName.slice(0, rawName.indexOf(":")) : null;
|
|
243
|
+
for (const [key, value] of rawAttrs) {
|
|
244
|
+
if (key.startsWith("xmlns:") && value === "http://www.w3.org/1999/xlink") {
|
|
245
|
+
this.xlinkPrefixes.add(key.slice(6));
|
|
246
|
+
}
|
|
247
|
+
if (key === "xmlns:cy" || key.startsWith("cy:")) {
|
|
248
|
+
this.cytoscape = true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
this.rootPrefix = prefix;
|
|
252
|
+
const nsKey = prefix === null ? "xmlns" : `xmlns:${prefix}`;
|
|
253
|
+
this.xgmmlNamespace = rawAttrs.get(nsKey) === XGMML_NAMESPACE;
|
|
254
|
+
if (local !== "graph") {
|
|
255
|
+
this.report.fail(XGMML_ISSUE.NO_GRAPH, `the root element is <${rawName}>, not <graph>; an XGMML graph embedded in another document is not read`, { line });
|
|
256
|
+
}
|
|
257
|
+
const graph = this.newGraph(this.normalise(rawAttrs), line, null, null);
|
|
258
|
+
this.rootGraph = graph;
|
|
259
|
+
this.frames.push({ kind: "graph", graph });
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* A graph record, registered in the document.
|
|
263
|
+
* @param attrs - its attributes
|
|
264
|
+
* @param line - the line
|
|
265
|
+
* @param owner - the node whose att holds it, or null
|
|
266
|
+
* @param parent - the graph it is nested in, or null
|
|
267
|
+
* @returns the record
|
|
268
|
+
*/
|
|
269
|
+
newGraph(attrs, line, owner, parent) {
|
|
270
|
+
const graph = {
|
|
271
|
+
id: attrs.get("id") ?? null,
|
|
272
|
+
label: attrs.get("label") ?? null,
|
|
273
|
+
href: attrs.get("xlink:href") ?? null,
|
|
274
|
+
registered: attrs.get("cy:registered") ?? null,
|
|
275
|
+
attrs,
|
|
276
|
+
atts: [],
|
|
277
|
+
graphics: null,
|
|
278
|
+
owner,
|
|
279
|
+
parent,
|
|
280
|
+
members: [],
|
|
281
|
+
subgraphs: [],
|
|
282
|
+
line,
|
|
283
|
+
};
|
|
284
|
+
if (!this.cytoscape) {
|
|
285
|
+
for (const key of attrs.keys()) {
|
|
286
|
+
if (key.startsWith("cy:")) {
|
|
287
|
+
this.cytoscape = true;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
this.graphs.push(graph);
|
|
292
|
+
return graph;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* A child of `<graph>`.
|
|
296
|
+
* @param graph - the graph
|
|
297
|
+
* @param rawName - the name as written
|
|
298
|
+
* @param name - its local name
|
|
299
|
+
* @param attrs - its attributes
|
|
300
|
+
* @param line - the line
|
|
301
|
+
*/
|
|
302
|
+
startInGraph(graph, rawName, name, attrs, line) {
|
|
303
|
+
switch (name) {
|
|
304
|
+
case "node":
|
|
305
|
+
this.beginNode(graph, attrs, line);
|
|
306
|
+
return;
|
|
307
|
+
case "edge":
|
|
308
|
+
this.beginEdge(graph, attrs, line);
|
|
309
|
+
return;
|
|
310
|
+
case "att": {
|
|
311
|
+
const att = this.newAtt(attrs, line);
|
|
312
|
+
graph.atts.push(att);
|
|
313
|
+
this.beginAtt(att, attrs, "graph");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
case "graphics":
|
|
317
|
+
this.beginGraphics(graph, attrs);
|
|
318
|
+
return;
|
|
319
|
+
default:
|
|
320
|
+
this.unknownElement(rawName, line);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* A child of `<node>` or `<edge>`.
|
|
325
|
+
* @param element - the node or edge
|
|
326
|
+
* @param rawName - the name as written
|
|
327
|
+
* @param name - its local name
|
|
328
|
+
* @param attrs - its attributes
|
|
329
|
+
* @param line - the line
|
|
330
|
+
*/
|
|
331
|
+
startInElement(element, rawName, name, attrs, line) {
|
|
332
|
+
switch (name) {
|
|
333
|
+
case "att": {
|
|
334
|
+
const att = this.newAtt(attrs, line);
|
|
335
|
+
element.atts.push(att);
|
|
336
|
+
this.beginAtt(att, attrs, element.kind);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
case "graphics":
|
|
340
|
+
this.beginGraphics(element, attrs);
|
|
341
|
+
return;
|
|
342
|
+
default:
|
|
343
|
+
this.unknownElement(rawName, line);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* A child of `<att>`: a child att, a nested graph, or foreign XML.
|
|
348
|
+
* @param frame - the att frame
|
|
349
|
+
* @param rawName - the name as written
|
|
350
|
+
* @param name - its local name
|
|
351
|
+
* @param attrs - its attributes
|
|
352
|
+
* @param line - the line
|
|
353
|
+
*/
|
|
354
|
+
startInAtt(frame, rawName, name, attrs, line) {
|
|
355
|
+
const { att } = frame;
|
|
356
|
+
const xgmml = this.isXgmmlName(rawName);
|
|
357
|
+
if (name === "att" && xgmml) {
|
|
358
|
+
const child = this.newAtt(attrs, line);
|
|
359
|
+
att.children.push(child);
|
|
360
|
+
this.beginAtt(child, attrs, frame.owner === "graphics" ? "graphics" : "att");
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (name === "graph" && xgmml) {
|
|
364
|
+
att.hasGraph = true;
|
|
365
|
+
this.beginNestedGraph(frame, attrs, line);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
// a second foreign element of the same att is appended to the first
|
|
369
|
+
att.xml ?? (att.xml = "");
|
|
370
|
+
const capture = { kind: "capture", att, depth: 0 };
|
|
371
|
+
this.frames.push(capture);
|
|
372
|
+
this.capture(capture, rawName, attrs);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Whether an element name inside an att is XGMML's own (unprefixed, or with the root's prefix,
|
|
376
|
+
* as in a document whose root is `<xgmml:graph>`) rather than foreign XML such as `rdf:RDF`.
|
|
377
|
+
* @param rawName - the name as written
|
|
378
|
+
* @returns true for an XGMML element name
|
|
379
|
+
*/
|
|
380
|
+
isXgmmlName(rawName) {
|
|
381
|
+
const colon = rawName.indexOf(":");
|
|
382
|
+
return colon < 0 || (this.rootPrefix !== null && rawName.slice(0, colon) === this.rootPrefix);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* A `<graph>` inside an `<att>`: a subnetwork of the root, a group or nested-network pointer
|
|
386
|
+
* of a node, or (in an edge) a graph with no model.
|
|
387
|
+
* @param frame - the att frame
|
|
388
|
+
* @param attrs - the graph's attributes
|
|
389
|
+
* @param line - the line
|
|
390
|
+
*/
|
|
391
|
+
beginNestedGraph(frame, attrs, line) {
|
|
392
|
+
const holder = this.attHolder();
|
|
393
|
+
if (holder === null || holder.kind === "edge" || frame.owner === "graphics") {
|
|
394
|
+
this.report.warnOnce("unsupported", XGMML_ISSUE.EDGE_NESTED_GRAPH, "a graph nested in an edge's (or a graphics) att has no model and was skipped with its content", { line });
|
|
395
|
+
this.frames.push({ kind: "skip" });
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (holder.kind === "node") {
|
|
399
|
+
const graph = this.newGraph(attrs, line, holder.node, holder.node.graph);
|
|
400
|
+
holder.node.nested.push(graph);
|
|
401
|
+
this.frames.push({ kind: "graph", graph });
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const graph = this.newGraph(attrs, line, null, holder.graph);
|
|
405
|
+
holder.graph.subgraphs.push(graph);
|
|
406
|
+
this.frames.push({ kind: "graph", graph });
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* The innermost node, edge or graph frame below the open atts.
|
|
410
|
+
* @returns the frame, or null
|
|
411
|
+
*/
|
|
412
|
+
attHolder() {
|
|
413
|
+
for (let i = this.frames.length - 1; i >= 0; i--) {
|
|
414
|
+
const frame = this.frames[i];
|
|
415
|
+
if (frame.kind === "graph" || frame.kind === "node" || frame.kind === "edge") {
|
|
416
|
+
return frame;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Report an element XGMML does not define here and skip its subtree.
|
|
423
|
+
* @param rawName - the name as written
|
|
424
|
+
* @param line - the line
|
|
425
|
+
*/
|
|
426
|
+
unknownElement(rawName, line) {
|
|
427
|
+
this.report.warnOnce("unsupported", XGMML_ISSUE.UNKNOWN_ELEMENT, `element <${rawName}> is not XGMML here and was skipped with its content`, { line, element: rawName }, `${XGMML_ISSUE.UNKNOWN_ELEMENT}:${rawName}`);
|
|
428
|
+
this.frames.push({ kind: "skip" });
|
|
429
|
+
}
|
|
430
|
+
// ------------------------------------------------------------------ nodes and edges
|
|
431
|
+
/**
|
|
432
|
+
* Open a `<node>`.
|
|
433
|
+
* @param graph - the graph it is in
|
|
434
|
+
* @param attrs - its attributes
|
|
435
|
+
* @param line - the line
|
|
436
|
+
*/
|
|
437
|
+
beginNode(graph, attrs, line) {
|
|
438
|
+
const href = attrs.get("xlink:href") ?? null;
|
|
439
|
+
let id = attrs.get("id") ?? null;
|
|
440
|
+
const labelAttr = attrs.get("label") ?? null;
|
|
441
|
+
const nameAttr = attrs.get("name") ?? null;
|
|
442
|
+
const label = labelAttr ?? nameAttr;
|
|
443
|
+
if (href === null && id === null) {
|
|
444
|
+
if (label === null || label.length === 0) {
|
|
445
|
+
this.report.counts.skippedNodes++;
|
|
446
|
+
this.report.error("missing-value", XGMML_ISSUE.MISSING_ID, "<node> without an id or a label", {
|
|
447
|
+
line,
|
|
448
|
+
});
|
|
449
|
+
this.frames.push({ kind: "skip" });
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
this.report.warning("validation-error", XGMML_ISSUE.ID_FROM_LABEL, `<node> without an id: its label "${label}" is used as the id`, { line, element: label });
|
|
453
|
+
id = label;
|
|
454
|
+
}
|
|
455
|
+
const node = {
|
|
456
|
+
kind: "node",
|
|
457
|
+
id: href === null ? id : null,
|
|
458
|
+
href,
|
|
459
|
+
label,
|
|
460
|
+
name: labelAttr !== null ? nameAttr : null,
|
|
461
|
+
xmlAttrs: otherAttributes(attrs, NODE_READ),
|
|
462
|
+
atts: [],
|
|
463
|
+
graphics: null,
|
|
464
|
+
x: null,
|
|
465
|
+
y: null,
|
|
466
|
+
z: null,
|
|
467
|
+
nested: [],
|
|
468
|
+
graph,
|
|
469
|
+
line,
|
|
470
|
+
viewId: attrs.get("cy:nodeId") ?? null,
|
|
471
|
+
};
|
|
472
|
+
graph.members.push(node);
|
|
473
|
+
if (href === null) {
|
|
474
|
+
this.nodes.push(node);
|
|
475
|
+
}
|
|
476
|
+
this.frames.push({ kind: "node", node });
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Open an `<edge>`.
|
|
480
|
+
* @param graph - the graph it is in
|
|
481
|
+
* @param attrs - its attributes
|
|
482
|
+
* @param line - the line
|
|
483
|
+
*/
|
|
484
|
+
beginEdge(graph, attrs, line) {
|
|
485
|
+
const href = attrs.get("xlink:href") ?? null;
|
|
486
|
+
const edge = {
|
|
487
|
+
kind: "edge",
|
|
488
|
+
id: href === null ? (attrs.get("id") ?? null) : null,
|
|
489
|
+
href,
|
|
490
|
+
source: attrs.get("source") ?? null,
|
|
491
|
+
target: attrs.get("target") ?? null,
|
|
492
|
+
label: attrs.get("label") ?? null,
|
|
493
|
+
name: attrs.get("name") ?? null,
|
|
494
|
+
weight: attrs.get("weight") ?? null,
|
|
495
|
+
directed: attrs.get("cy:directed") ?? null,
|
|
496
|
+
xmlAttrs: otherAttributes(attrs, EDGE_READ),
|
|
497
|
+
atts: [],
|
|
498
|
+
graphics: null,
|
|
499
|
+
graph,
|
|
500
|
+
line,
|
|
501
|
+
viewId: attrs.get("cy:edgeId") ?? null,
|
|
502
|
+
};
|
|
503
|
+
graph.members.push(edge);
|
|
504
|
+
if (href === null) {
|
|
505
|
+
this.edges.push(edge);
|
|
506
|
+
}
|
|
507
|
+
this.frames.push({ kind: "edge", edge });
|
|
508
|
+
}
|
|
509
|
+
// ------------------------------------------------------------------ atts
|
|
510
|
+
/**
|
|
511
|
+
* An att record from its XML attributes.
|
|
512
|
+
* @param attrs - the attributes
|
|
513
|
+
* @param line - the line
|
|
514
|
+
* @returns the record
|
|
515
|
+
*/
|
|
516
|
+
newAtt(attrs, line) {
|
|
517
|
+
const extra = {};
|
|
518
|
+
for (const [key, value] of attrs) {
|
|
519
|
+
if (!ATT_READ.has(key) && key !== "xmlns" && !key.startsWith("xmlns:")) {
|
|
520
|
+
extra[key] = value;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
name: attrs.get("name") ?? attrs.get("label") ?? null,
|
|
525
|
+
type: attrs.get("type") ?? null,
|
|
526
|
+
cyType: attrs.get("cy:type") ?? null,
|
|
527
|
+
elementType: attrs.get("cy:elementType") ?? null,
|
|
528
|
+
value: attrs.get("value") ?? null,
|
|
529
|
+
hidden: isCyTrue(attrs.get("cy:hidden")),
|
|
530
|
+
equation: isCyTrue(attrs.get("cy:equation")),
|
|
531
|
+
children: [],
|
|
532
|
+
extra,
|
|
533
|
+
xml: null,
|
|
534
|
+
hasGraph: false,
|
|
535
|
+
text: "",
|
|
536
|
+
line,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Open an `<att>`.
|
|
541
|
+
* @param att - the record
|
|
542
|
+
* @param _attrs - its attributes
|
|
543
|
+
* @param owner - what holds it
|
|
544
|
+
*/
|
|
545
|
+
beginAtt(att, _attrs, owner) {
|
|
546
|
+
this.frames.push({ kind: "att", att, owner });
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Serialize one start tag of foreign XML into the capturing att.
|
|
550
|
+
* @param frame - the capture frame
|
|
551
|
+
* @param rawName - the name as written
|
|
552
|
+
* @param attrs - its attributes
|
|
553
|
+
*/
|
|
554
|
+
capture(frame, rawName, attrs) {
|
|
555
|
+
let tag = `<${rawName}`;
|
|
556
|
+
for (const [key, value] of attrs) {
|
|
557
|
+
tag += ` ${key}="${escapeXmlAttribute(value)}"`;
|
|
558
|
+
}
|
|
559
|
+
frame.att.xml = `${frame.att.xml ?? ""}${tag}>`;
|
|
560
|
+
frame.depth++;
|
|
561
|
+
if (frame.att.name === "networkMetadata") {
|
|
562
|
+
this.rdfElement = localName(rawName);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Keep the text of an RDF metadata field.
|
|
567
|
+
* @param text - the text
|
|
568
|
+
*/
|
|
569
|
+
captureRdfText(text) {
|
|
570
|
+
if (this.rdfElement !== null && !isWhitespace(text)) {
|
|
571
|
+
this.rdf[this.rdfElement] = (this.rdf[this.rdfElement] ?? "") + text;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// ------------------------------------------------------------------ graphics
|
|
575
|
+
/**
|
|
576
|
+
* Open a `<graphics>`: x, y and z (draft and Cytoscape) are read as coordinates; every other
|
|
577
|
+
* attribute is kept as written, merged key by key with an earlier `<graphics>`.
|
|
578
|
+
* @param target - the node, edge or graph
|
|
579
|
+
* @param attrs - its attributes
|
|
580
|
+
*/
|
|
581
|
+
beginGraphics(target, attrs) {
|
|
582
|
+
const graphics = { ...(target.graphics ?? {}) };
|
|
583
|
+
for (const [key, value] of attrs) {
|
|
584
|
+
if ("x" in target && (key === "x" || key === "y" || key === "z")) {
|
|
585
|
+
target[key] = value;
|
|
586
|
+
}
|
|
587
|
+
else if (key !== "xmlns" && !key.startsWith("xmlns:")) {
|
|
588
|
+
graphics[key] = value;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
target.graphics = graphics;
|
|
592
|
+
this.frames.push({ kind: "graphics", target });
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* A child of `<graphics>`: a visual-property att, a `<center>` or a `<Line>`.
|
|
596
|
+
* @param target - the graphics target
|
|
597
|
+
* @param rawName - the name as written
|
|
598
|
+
* @param name - its local name
|
|
599
|
+
* @param attrs - its attributes
|
|
600
|
+
* @param line - the line
|
|
601
|
+
*/
|
|
602
|
+
startInGraphics(target, rawName, name, attrs, line) {
|
|
603
|
+
switch (name) {
|
|
604
|
+
case "att": {
|
|
605
|
+
this.frames.push({ kind: "att", att: this.newAtt(attrs, line), owner: "graphics" });
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
case "center":
|
|
609
|
+
if ("x" in target) {
|
|
610
|
+
target.x ?? (target.x = attrs.get("x") ?? null);
|
|
611
|
+
target.y ?? (target.y = attrs.get("y") ?? null);
|
|
612
|
+
target.z ?? (target.z = attrs.get("z") ?? null);
|
|
613
|
+
}
|
|
614
|
+
this.frames.push({ kind: "skip" });
|
|
615
|
+
return;
|
|
616
|
+
case "Line":
|
|
617
|
+
case "line":
|
|
618
|
+
this.frames.push({ kind: "line", points: [] });
|
|
619
|
+
return;
|
|
620
|
+
default:
|
|
621
|
+
this.unknownElement(rawName, line);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Keep a visual-property att of a `<graphics>` in its record, as written. A session view's
|
|
626
|
+
* `<att name="z">` repeats z and is read as z when the attributes gave none.
|
|
627
|
+
* @param target - the graphics target
|
|
628
|
+
* @param att - the att
|
|
629
|
+
*/
|
|
630
|
+
graphicsAtt(target, att) {
|
|
631
|
+
if (att.name === "z" && "z" in target && att.children.length === 0) {
|
|
632
|
+
target.z ?? (target.z = att.value);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (att.name === null) {
|
|
636
|
+
this.report.warnOnce("validation-error", XGMML_ISSUE.BAD_ATT, "a graphics <att> without a name was skipped", { line: att.line }, `${XGMML_ISSUE.BAD_ATT}:graphics-name`);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
target.graphics = { ...(target.graphics ?? {}), [att.name]: attJson(att) };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Whether an att's children are list items rather than named fields: they have no names, repeat
|
|
644
|
+
* the parent's name (Cytoscape 3 lists), or are several that share one name (2.x bend handles).
|
|
645
|
+
* @param att - the att
|
|
646
|
+
* @returns true for a list
|
|
647
|
+
*/
|
|
648
|
+
export function isListLike(att) {
|
|
649
|
+
const names = new Set(att.children.map((c) => c.name));
|
|
650
|
+
if (names.size !== 1) {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
const [only] = names;
|
|
654
|
+
return only === null || only === att.name || att.children.length > 1;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* A graphics att as plain data, values as written: an att with children is an array when they
|
|
658
|
+
* are list items (isListLike), else a record by name; a childless att is its value,
|
|
659
|
+
* else its other XML attributes (a 2.x bend handle's x and y), else null.
|
|
660
|
+
* @param att - the att
|
|
661
|
+
* @returns the value
|
|
662
|
+
*/
|
|
663
|
+
export function attJson(att) {
|
|
664
|
+
if (att.children.length === 0) {
|
|
665
|
+
if (att.value !== null) {
|
|
666
|
+
return att.value;
|
|
667
|
+
}
|
|
668
|
+
return Object.keys(att.extra).length > 0 ? { ...att.extra } : null;
|
|
669
|
+
}
|
|
670
|
+
if (isListLike(att)) {
|
|
671
|
+
return att.children.map(attJson);
|
|
672
|
+
}
|
|
673
|
+
const out = {};
|
|
674
|
+
for (const child of att.children) {
|
|
675
|
+
out[child.name ?? ""] = attJson(child);
|
|
676
|
+
}
|
|
677
|
+
return out;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* The XML attributes of an element the parser does not read itself, without namespace
|
|
681
|
+
* declarations.
|
|
682
|
+
* @param attrs - the attributes
|
|
683
|
+
* @param read - the names the parser reads
|
|
684
|
+
* @returns the others, in document order
|
|
685
|
+
*/
|
|
686
|
+
function otherAttributes(attrs, read) {
|
|
687
|
+
const out = [];
|
|
688
|
+
for (const [key, value] of attrs) {
|
|
689
|
+
if (!read.has(key) && key !== "xmlns" && !key.startsWith("xmlns:")) {
|
|
690
|
+
out.push([key, value]);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return out;
|
|
694
|
+
}
|
|
695
|
+
//# sourceMappingURL=document.js.map
|