@graphty/graph-io 0.3.19 → 0.3.21

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.
Files changed (201) hide show
  1. package/README.md +49 -4
  2. package/dist/chunks/{escape-CWExcecC.js → escape-B-tkk_Cl.js} +74 -16
  3. package/dist/chunks/escape-B-tkk_Cl.js.map +1 -0
  4. package/dist/chunks/{writer-DQiKgQJc.js → export-Bh60Dv-n.js} +5 -79
  5. package/dist/chunks/export-Bh60Dv-n.js.map +1 -0
  6. package/dist/chunks/{exporter-CbMZyVt-.js → exporter-BGrsWamJ.js} +7 -6
  7. package/dist/chunks/{exporter-CbMZyVt-.js.map → exporter-BGrsWamJ.js.map} +1 -1
  8. package/dist/chunks/exporter-DIeGJXAZ.js +834 -0
  9. package/dist/chunks/exporter-DIeGJXAZ.js.map +1 -0
  10. package/dist/chunks/{importer-BNFuV1-K.js → importer-BWY2FFc5.js} +28 -7
  11. package/dist/chunks/importer-BWY2FFc5.js.map +1 -0
  12. package/dist/chunks/importer-DD-xv9_Y.js +1471 -0
  13. package/dist/chunks/importer-DD-xv9_Y.js.map +1 -0
  14. package/dist/chunks/{importer-BAP4PrxR.js → importer-DET1tg6s.js} +30 -20
  15. package/dist/chunks/importer-DET1tg6s.js.map +1 -0
  16. package/dist/chunks/{importer-ByPGO-09.js → importer-DEcKhsmQ.js} +2 -2
  17. package/dist/chunks/{importer-ByPGO-09.js.map → importer-DEcKhsmQ.js.map} +1 -1
  18. package/dist/chunks/{importer-BW-Ft2ps.js → importer-DIrbbnAf.js} +6 -5
  19. package/dist/chunks/{importer-BW-Ft2ps.js.map → importer-DIrbbnAf.js.map} +1 -1
  20. package/dist/chunks/importer-D_e7e7LX.js +3055 -0
  21. package/dist/chunks/importer-D_e7e7LX.js.map +1 -0
  22. package/dist/chunks/{importer-Bm1_5vPS.js → importer-eUREhBGj.js} +11 -8
  23. package/dist/chunks/importer-eUREhBGj.js.map +1 -0
  24. package/dist/chunks/{importer-DOepkgnG.js → importer-vi3bSdtw.js} +4 -4
  25. package/dist/chunks/{importer-DOepkgnG.js.map → importer-vi3bSdtw.js.map} +1 -1
  26. package/dist/chunks/{importer-BVGtU1NA.js → importer-zGLo8Dg_.js} +6 -5
  27. package/dist/chunks/{importer-BVGtU1NA.js.map → importer-zGLo8Dg_.js.map} +1 -1
  28. package/dist/chunks/{json-elements-CZY1wiZh.js → json-elements-DDS8N2Dd.js} +2 -2
  29. package/dist/chunks/{json-elements-CZY1wiZh.js.map → json-elements-DDS8N2Dd.js.map} +1 -1
  30. package/dist/chunks/{records-BzNicMsf.js → records-dbRkxwaq.js} +2 -2
  31. package/dist/chunks/{records-BzNicMsf.js.map → records-dbRkxwaq.js.map} +1 -1
  32. package/dist/chunks/{report-BcWboivV.js → report-B1z4WT9e.js} +143 -111
  33. package/dist/chunks/{report-BcWboivV.js.map → report-B1z4WT9e.js.map} +1 -1
  34. package/dist/chunks/{weights-CwISIpCP.js → weights-Dzba96G3.js} +5 -5
  35. package/dist/chunks/{weights-CwISIpCP.js.map → weights-Dzba96G3.js.map} +1 -1
  36. package/dist/chunks/writer-C7flM-Ih.js +77 -0
  37. package/dist/chunks/writer-C7flM-Ih.js.map +1 -0
  38. package/dist/csv.js +6 -5
  39. package/dist/csv.js.map +1 -1
  40. package/dist/cx.js +1 -1
  41. package/dist/cx2.js +2 -2
  42. package/dist/cys.d.ts +1 -0
  43. package/dist/cys.js +6 -0
  44. package/dist/cys.js.map +1 -0
  45. package/dist/dot.js +1 -1
  46. package/dist/gexf.js +5 -4
  47. package/dist/gexf.js.map +1 -1
  48. package/dist/gml.js +12 -14
  49. package/dist/gml.js.map +1 -1
  50. package/dist/graph-io.js +288 -141
  51. package/dist/graph-io.js.map +1 -1
  52. package/dist/graphml.js +1 -1
  53. package/dist/json.js +1 -1
  54. package/dist/neo4j.js +6 -5
  55. package/dist/neo4j.js.map +1 -1
  56. package/dist/obo.js +1 -1
  57. package/dist/pajek.js +1 -1
  58. package/dist/src/common/cell-budget.d.ts +84 -0
  59. package/dist/src/common/cell-budget.d.ts.map +1 -0
  60. package/dist/src/common/cell-budget.js +138 -0
  61. package/dist/src/common/cell-budget.js.map +1 -0
  62. package/dist/src/common/input.d.ts +10 -0
  63. package/dist/src/common/input.d.ts.map +1 -1
  64. package/dist/src/common/input.js +39 -0
  65. package/dist/src/common/input.js.map +1 -1
  66. package/dist/src/common/options.d.ts +6 -0
  67. package/dist/src/common/options.d.ts.map +1 -1
  68. package/dist/src/common/options.js +1 -1
  69. package/dist/src/common/options.js.map +1 -1
  70. package/dist/src/common/xml.d.ts +37 -3
  71. package/dist/src/common/xml.d.ts.map +1 -1
  72. package/dist/src/common/xml.js +86 -6
  73. package/dist/src/common/xml.js.map +1 -1
  74. package/dist/src/common/zip.d.ts +92 -0
  75. package/dist/src/common/zip.d.ts.map +1 -0
  76. package/dist/src/common/zip.js +399 -0
  77. package/dist/src/common/zip.js.map +1 -0
  78. package/dist/src/formats/cx/importer.d.ts.map +1 -1
  79. package/dist/src/formats/cx/importer.js +26 -17
  80. package/dist/src/formats/cx/importer.js.map +1 -1
  81. package/dist/src/formats/cys/constants.d.ts +68 -0
  82. package/dist/src/formats/cys/constants.d.ts.map +1 -0
  83. package/dist/src/formats/cys/constants.js +76 -0
  84. package/dist/src/formats/cys/constants.js.map +1 -0
  85. package/dist/src/formats/cys/importer.d.ts +38 -0
  86. package/dist/src/formats/cys/importer.d.ts.map +1 -0
  87. package/dist/src/formats/cys/importer.js +830 -0
  88. package/dist/src/formats/cys/importer.js.map +1 -0
  89. package/dist/src/formats/cys/index.d.ts +7 -0
  90. package/dist/src/formats/cys/index.d.ts.map +1 -0
  91. package/dist/src/formats/cys/index.js +7 -0
  92. package/dist/src/formats/cys/index.js.map +1 -0
  93. package/dist/src/formats/cys/session.d.ts +132 -0
  94. package/dist/src/formats/cys/session.d.ts.map +1 -0
  95. package/dist/src/formats/cys/session.js +315 -0
  96. package/dist/src/formats/cys/session.js.map +1 -0
  97. package/dist/src/formats/cys/tables.d.ts +90 -0
  98. package/dist/src/formats/cys/tables.d.ts.map +1 -0
  99. package/dist/src/formats/cys/tables.js +293 -0
  100. package/dist/src/formats/cys/tables.js.map +1 -0
  101. package/dist/src/formats/dot/exporter.d.ts +2 -0
  102. package/dist/src/formats/dot/exporter.d.ts.map +1 -1
  103. package/dist/src/formats/dot/exporter.js +12 -0
  104. package/dist/src/formats/dot/exporter.js.map +1 -1
  105. package/dist/src/formats/dot/importer.js +6 -2
  106. package/dist/src/formats/dot/importer.js.map +1 -1
  107. package/dist/src/formats/gexf/importer.d.ts +2 -2
  108. package/dist/src/formats/gexf/importer.d.ts.map +1 -1
  109. package/dist/src/formats/gexf/importer.js +1 -1
  110. package/dist/src/formats/gexf/importer.js.map +1 -1
  111. package/dist/src/formats/gml/exporter.d.ts +0 -8
  112. package/dist/src/formats/gml/exporter.d.ts.map +1 -1
  113. package/dist/src/formats/gml/exporter.js +7 -17
  114. package/dist/src/formats/gml/exporter.js.map +1 -1
  115. package/dist/src/formats/json/exporter.d.ts.map +1 -1
  116. package/dist/src/formats/json/exporter.js +3 -1
  117. package/dist/src/formats/json/exporter.js.map +1 -1
  118. package/dist/src/formats/json/importer.d.ts.map +1 -1
  119. package/dist/src/formats/json/importer.js +3 -2
  120. package/dist/src/formats/json/importer.js.map +1 -1
  121. package/dist/src/formats/xgmml/columns.d.ts +152 -0
  122. package/dist/src/formats/xgmml/columns.d.ts.map +1 -0
  123. package/dist/src/formats/xgmml/columns.js +593 -0
  124. package/dist/src/formats/xgmml/columns.js.map +1 -0
  125. package/dist/src/formats/xgmml/constants.d.ts +196 -0
  126. package/dist/src/formats/xgmml/constants.d.ts.map +1 -0
  127. package/dist/src/formats/xgmml/constants.js +197 -0
  128. package/dist/src/formats/xgmml/constants.js.map +1 -0
  129. package/dist/src/formats/xgmml/document.d.ts +361 -0
  130. package/dist/src/formats/xgmml/document.d.ts.map +1 -0
  131. package/dist/src/formats/xgmml/document.js +695 -0
  132. package/dist/src/formats/xgmml/document.js.map +1 -0
  133. package/dist/src/formats/xgmml/emit.d.ts +341 -0
  134. package/dist/src/formats/xgmml/emit.d.ts.map +1 -0
  135. package/dist/src/formats/xgmml/emit.js +1275 -0
  136. package/dist/src/formats/xgmml/emit.js.map +1 -0
  137. package/dist/src/formats/xgmml/exporter.d.ts +24 -0
  138. package/dist/src/formats/xgmml/exporter.d.ts.map +1 -0
  139. package/dist/src/formats/xgmml/exporter.js +999 -0
  140. package/dist/src/formats/xgmml/exporter.js.map +1 -0
  141. package/dist/src/formats/xgmml/importer.d.ts +58 -0
  142. package/dist/src/formats/xgmml/importer.d.ts.map +1 -0
  143. package/dist/src/formats/xgmml/importer.js +360 -0
  144. package/dist/src/formats/xgmml/importer.js.map +1 -0
  145. package/dist/src/formats/xgmml/index.d.ts +8 -0
  146. package/dist/src/formats/xgmml/index.d.ts.map +1 -0
  147. package/dist/src/formats/xgmml/index.js +8 -0
  148. package/dist/src/formats/xgmml/index.js.map +1 -0
  149. package/dist/src/formats/xgmml/values.d.ts +60 -0
  150. package/dist/src/formats/xgmml/values.d.ts.map +1 -0
  151. package/dist/src/formats/xgmml/values.js +148 -0
  152. package/dist/src/formats/xgmml/values.js.map +1 -0
  153. package/dist/src/index.d.ts +2 -0
  154. package/dist/src/index.d.ts.map +1 -1
  155. package/dist/src/index.js +2 -0
  156. package/dist/src/index.js.map +1 -1
  157. package/dist/src/registry.d.ts +7 -0
  158. package/dist/src/registry.d.ts.map +1 -1
  159. package/dist/src/registry.js +25 -10
  160. package/dist/src/registry.js.map +1 -1
  161. package/dist/src/sniff.d.ts +1 -1
  162. package/dist/src/sniff.d.ts.map +1 -1
  163. package/dist/src/sniff.js +2 -0
  164. package/dist/src/sniff.js.map +1 -1
  165. package/dist/xgmml.d.ts +1 -0
  166. package/dist/xgmml.js +9 -0
  167. package/dist/xgmml.js.map +1 -0
  168. package/package.json +15 -2
  169. package/src/common/cell-budget.ts +170 -0
  170. package/src/common/input.ts +40 -0
  171. package/src/common/options.ts +1 -1
  172. package/src/common/xml.ts +127 -6
  173. package/src/common/zip.ts +472 -0
  174. package/src/formats/cx/importer.ts +27 -19
  175. package/src/formats/cys/constants.ts +99 -0
  176. package/src/formats/cys/importer.ts +1100 -0
  177. package/src/formats/cys/index.ts +7 -0
  178. package/src/formats/cys/session.ts +428 -0
  179. package/src/formats/cys/tables.ts +400 -0
  180. package/src/formats/dot/exporter.ts +17 -0
  181. package/src/formats/dot/importer.ts +6 -2
  182. package/src/formats/gexf/importer.ts +0 -1
  183. package/src/formats/gml/exporter.ts +7 -18
  184. package/src/formats/json/exporter.ts +3 -1
  185. package/src/formats/json/importer.ts +3 -2
  186. package/src/formats/xgmml/columns.ts +747 -0
  187. package/src/formats/xgmml/constants.ts +265 -0
  188. package/src/formats/xgmml/document.ts +975 -0
  189. package/src/formats/xgmml/emit.ts +1563 -0
  190. package/src/formats/xgmml/exporter.ts +1215 -0
  191. package/src/formats/xgmml/importer.ts +491 -0
  192. package/src/formats/xgmml/index.ts +8 -0
  193. package/src/formats/xgmml/values.ts +183 -0
  194. package/src/index.ts +9 -0
  195. package/src/registry.ts +41 -15
  196. package/src/sniff.ts +5 -1
  197. package/dist/chunks/escape-CWExcecC.js.map +0 -1
  198. package/dist/chunks/importer-BAP4PrxR.js.map +0 -1
  199. package/dist/chunks/importer-BNFuV1-K.js.map +0 -1
  200. package/dist/chunks/importer-Bm1_5vPS.js.map +0 -1
  201. package/dist/chunks/writer-DQiKgQJc.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