@loaders.gl/graphs 4.5.3

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 (54) hide show
  1. package/LICENSE +41 -0
  2. package/README.md +66 -0
  3. package/dist/dot-loader-types.d.ts +16 -0
  4. package/dist/dot-loader-types.d.ts.map +1 -0
  5. package/dist/dot-loader-types.js +20 -0
  6. package/dist/dot-loader-types.js.map +1 -0
  7. package/dist/dot-loader.cjs +732 -0
  8. package/dist/dot-loader.cjs.map +7 -0
  9. package/dist/dot-loader.d.ts +19 -0
  10. package/dist/dot-loader.d.ts.map +1 -0
  11. package/dist/dot-loader.js +13 -0
  12. package/dist/dot-loader.js.map +1 -0
  13. package/dist/dot-types.d.ts +32 -0
  14. package/dist/dot-types.d.ts.map +1 -0
  15. package/dist/dot-types.js +5 -0
  16. package/dist/dot-types.js.map +1 -0
  17. package/dist/graph-types.d.ts +28 -0
  18. package/dist/graph-types.d.ts.map +1 -0
  19. package/dist/graph-types.js +5 -0
  20. package/dist/graph-types.js.map +1 -0
  21. package/dist/graphml-loader-types.d.ts +16 -0
  22. package/dist/graphml-loader-types.d.ts.map +1 -0
  23. package/dist/graphml-loader-types.js +20 -0
  24. package/dist/graphml-loader-types.js.map +1 -0
  25. package/dist/graphml-loader.cjs +315 -0
  26. package/dist/graphml-loader.cjs.map +7 -0
  27. package/dist/graphml-loader.d.ts +19 -0
  28. package/dist/graphml-loader.d.ts.map +1 -0
  29. package/dist/graphml-loader.js +13 -0
  30. package/dist/graphml-loader.js.map +1 -0
  31. package/dist/index.cjs +1022 -0
  32. package/dist/index.cjs.map +7 -0
  33. package/dist/index.d.ts +5 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +6 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/lib/parse-dot.d.ts +4 -0
  38. package/dist/lib/parse-dot.d.ts.map +1 -0
  39. package/dist/lib/parse-dot.js +724 -0
  40. package/dist/lib/parse-dot.js.map +1 -0
  41. package/dist/lib/parse-graphml.d.ts +6 -0
  42. package/dist/lib/parse-graphml.d.ts.map +1 -0
  43. package/dist/lib/parse-graphml.js +296 -0
  44. package/dist/lib/parse-graphml.js.map +1 -0
  45. package/package.json +58 -0
  46. package/src/dot-loader-types.ts +24 -0
  47. package/src/dot-loader.ts +14 -0
  48. package/src/dot-types.ts +38 -0
  49. package/src/graph-types.ts +33 -0
  50. package/src/graphml-loader-types.ts +24 -0
  51. package/src/graphml-loader.ts +14 -0
  52. package/src/index.ts +8 -0
  53. package/src/lib/parse-dot.ts +924 -0
  54. package/src/lib/parse-graphml.ts +414 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1022 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // dist/index.js
21
+ var dist_exports = {};
22
+ __export(dist_exports, {
23
+ DOTLoader: () => DOTLoaderWithParser,
24
+ GraphMLLoader: () => GraphMLLoaderWithParser
25
+ });
26
+ module.exports = __toCommonJS(dist_exports);
27
+
28
+ // dist/graphml-loader-types.js
29
+ var VERSION = true ? "4.5.3" : "latest";
30
+ var GraphMLLoader = {
31
+ name: "GraphML",
32
+ id: "graphml",
33
+ module: "graphs",
34
+ version: VERSION,
35
+ extensions: ["graphml"],
36
+ mimeTypes: ["application/graphml+xml"],
37
+ text: true,
38
+ worker: false,
39
+ dataType: null,
40
+ batchType: null,
41
+ options: {}
42
+ };
43
+
44
+ // dist/lib/parse-graphml.js
45
+ var import_fast_xml_parser = require("fast-xml-parser");
46
+ var XML_ATTRIBUTE_PREFIX = "@_";
47
+ var XML_TEXT_KEY = "#text";
48
+ var graphmlParser = new import_fast_xml_parser.XMLParser({
49
+ ignoreAttributes: false,
50
+ attributeNamePrefix: XML_ATTRIBUTE_PREFIX,
51
+ textNodeName: XML_TEXT_KEY,
52
+ trimValues: true,
53
+ parseAttributeValue: false,
54
+ parseTagValue: false,
55
+ removeNSPrefix: true
56
+ });
57
+ function parseGraphML(graphml) {
58
+ const xmlText = decodeGraphML(graphml);
59
+ const document = graphmlParser.parse(xmlText);
60
+ const graphmlRoot = getGraphMLRoot(document);
61
+ if (!graphmlRoot) {
62
+ throw new Error("GraphML document does not contain a <graphml> element.");
63
+ }
64
+ const graphElement = getGraphElement(graphmlRoot);
65
+ if (!graphElement) {
66
+ throw new Error("GraphML document does not contain a <graph> element.");
67
+ }
68
+ const keyDefinitions = collectKeyDefinitions(graphmlRoot, graphElement);
69
+ const defaultDirected = parseEdgeDefault(graphElement[`${XML_ATTRIBUTE_PREFIX}edgedefault`]);
70
+ const nodes = normalizeArray(graphElement.node).map((node) => parseNode(node, keyDefinitions));
71
+ const edges = normalizeArray(graphElement.edge).map((edge, index) => parseEdge(edge, index, keyDefinitions, defaultDirected));
72
+ const filteredNodes = nodes.filter((node) => Boolean(node));
73
+ const filteredEdges = edges.filter((edge) => Boolean(edge));
74
+ return {
75
+ shape: "plain-graph-data",
76
+ nodes: filteredNodes,
77
+ edges: filteredEdges
78
+ };
79
+ }
80
+ function decodeGraphML(graphml) {
81
+ if (typeof graphml === "string") {
82
+ return graphml;
83
+ }
84
+ if (graphml instanceof Uint8Array) {
85
+ return new TextDecoder().decode(graphml);
86
+ }
87
+ if (graphml instanceof ArrayBuffer) {
88
+ return new TextDecoder().decode(new Uint8Array(graphml));
89
+ }
90
+ throw new Error("Unsupported GraphML input. Expected a string, ArrayBuffer, or Uint8Array.");
91
+ }
92
+ function getGraphMLRoot(document) {
93
+ const root = document.graphml;
94
+ if (isObject(root)) {
95
+ return root;
96
+ }
97
+ return null;
98
+ }
99
+ function getGraphElement(graphmlRoot) {
100
+ const graph = graphmlRoot.graph;
101
+ if (!graph) {
102
+ return null;
103
+ }
104
+ if (Array.isArray(graph)) {
105
+ const firstGraph = graph.find((entry) => isObject(entry));
106
+ return isObject(firstGraph) ? firstGraph : null;
107
+ }
108
+ return isObject(graph) ? graph : null;
109
+ }
110
+ function collectKeyDefinitions(graphmlRoot, graphElement) {
111
+ const keys = /* @__PURE__ */ new Map();
112
+ for (const candidate of [
113
+ ...normalizeArray(graphmlRoot.key),
114
+ ...normalizeArray(graphElement.key)
115
+ ]) {
116
+ if (isObject(candidate)) {
117
+ const id = String(candidate[`${XML_ATTRIBUTE_PREFIX}id`] ?? "").trim();
118
+ if (id) {
119
+ const domain = normalizeDomain(candidate[`${XML_ATTRIBUTE_PREFIX}for`]);
120
+ const name = String(candidate[`${XML_ATTRIBUTE_PREFIX}attr.name`] ?? id).trim();
121
+ const type = normalizeType(candidate[`${XML_ATTRIBUTE_PREFIX}attr.type`]);
122
+ const defaultNode = candidate.default ?? null;
123
+ const defaultValue = defaultNode !== null ? castDataValue(defaultNode, type) : void 0;
124
+ keys.set(id, { id, domain, name, type, defaultValue });
125
+ }
126
+ }
127
+ }
128
+ return keys;
129
+ }
130
+ function parseNode(node, keyDefinitions) {
131
+ if (!isObject(node)) {
132
+ return null;
133
+ }
134
+ const id = node[`${XML_ATTRIBUTE_PREFIX}id`];
135
+ if (typeof id !== "string") {
136
+ return null;
137
+ }
138
+ const attributes = buildAttributeBag("node", node.data, keyDefinitions);
139
+ const graphNode = {
140
+ id,
141
+ attributes: Object.keys(attributes).length > 0 ? attributes : void 0
142
+ };
143
+ const label = attributes.label;
144
+ if (typeof label === "string") {
145
+ graphNode.label = label;
146
+ }
147
+ return graphNode;
148
+ }
149
+ function parseEdge(edge, index, keyDefinitions, defaultDirected) {
150
+ if (!isObject(edge)) {
151
+ return null;
152
+ }
153
+ const sourceId = edge[`${XML_ATTRIBUTE_PREFIX}source`];
154
+ const targetId = edge[`${XML_ATTRIBUTE_PREFIX}target`];
155
+ if (typeof sourceId !== "string") {
156
+ return null;
157
+ }
158
+ if (typeof targetId !== "string") {
159
+ return null;
160
+ }
161
+ const rawId = edge[`${XML_ATTRIBUTE_PREFIX}id`];
162
+ const id = typeof rawId === "string" ? rawId : `edge-${index}`;
163
+ const directed = parseDirected(edge[`${XML_ATTRIBUTE_PREFIX}directed`], defaultDirected);
164
+ const attributes = buildAttributeBag("edge", edge.data, keyDefinitions);
165
+ const graphEdge = {
166
+ id,
167
+ sourceId,
168
+ targetId,
169
+ directed,
170
+ attributes: Object.keys(attributes).length > 0 ? attributes : void 0
171
+ };
172
+ const label = attributes.label;
173
+ if (typeof label === "string") {
174
+ graphEdge.label = label;
175
+ }
176
+ return graphEdge;
177
+ }
178
+ function buildAttributeBag(domain, data, keyDefinitions) {
179
+ const attributes = /* @__PURE__ */ Object.create(null);
180
+ for (const key of keyDefinitions.values()) {
181
+ if (key.domain === "all" || key.domain === domain) {
182
+ if (key.defaultValue !== void 0) {
183
+ attributes[key.name] = key.defaultValue;
184
+ }
185
+ }
186
+ }
187
+ for (const entry of normalizeArray(data)) {
188
+ assignAttributeFromDataEntry(entry, keyDefinitions, attributes);
189
+ }
190
+ return attributes;
191
+ }
192
+ function assignAttributeFromDataEntry(entry, keyDefinitions, attributes) {
193
+ if (!isObject(entry)) {
194
+ return;
195
+ }
196
+ const keyId = entry[`${XML_ATTRIBUTE_PREFIX}key`];
197
+ if (typeof keyId !== "string") {
198
+ return;
199
+ }
200
+ const definition = keyDefinitions.get(keyId);
201
+ const attributeName = (definition == null ? void 0 : definition.name) ?? keyId;
202
+ const value = castDataValue(entry, (definition == null ? void 0 : definition.type) ?? "string");
203
+ if (value !== void 0) {
204
+ attributes[attributeName] = value;
205
+ }
206
+ }
207
+ function castDataValue(value, type) {
208
+ if (value === null || typeof value === "undefined") {
209
+ return void 0;
210
+ }
211
+ const text = extractTextContent(value);
212
+ if (text === void 0) {
213
+ return void 0;
214
+ }
215
+ if (type === "boolean") {
216
+ return parseBoolean(text);
217
+ }
218
+ if (type === "int" || type === "long") {
219
+ const parsed = Number.parseInt(text, 10);
220
+ return Number.isNaN(parsed) ? text : parsed;
221
+ }
222
+ if (type === "float" || type === "double") {
223
+ const parsed = Number.parseFloat(text);
224
+ return Number.isNaN(parsed) ? text : parsed;
225
+ }
226
+ return text;
227
+ }
228
+ function extractTextContent(value) {
229
+ if (typeof value === "string") {
230
+ return value;
231
+ }
232
+ if (typeof value === "number" || typeof value === "boolean") {
233
+ return String(value);
234
+ }
235
+ if (Array.isArray(value)) {
236
+ return extractTextContent(value[0]);
237
+ }
238
+ if (isObject(value)) {
239
+ const text = value[XML_TEXT_KEY];
240
+ if (typeof text === "string") {
241
+ return text;
242
+ }
243
+ const nonAttributeEntries = Object.entries(value).filter(([key]) => !key.startsWith(XML_ATTRIBUTE_PREFIX));
244
+ if (nonAttributeEntries.length === 0) {
245
+ return void 0;
246
+ }
247
+ return JSON.stringify(Object.fromEntries(nonAttributeEntries));
248
+ }
249
+ return void 0;
250
+ }
251
+ function normalizeArray(value) {
252
+ if (!value) {
253
+ return [];
254
+ }
255
+ return Array.isArray(value) ? value : [value];
256
+ }
257
+ function isObject(value) {
258
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
259
+ }
260
+ function normalizeDomain(value) {
261
+ if (typeof value !== "string") {
262
+ return "all";
263
+ }
264
+ const lower = value.toLowerCase();
265
+ if (lower === "node" || lower === "edge" || lower === "graph" || lower === "all") {
266
+ return lower;
267
+ }
268
+ return "all";
269
+ }
270
+ function normalizeType(value) {
271
+ if (typeof value !== "string") {
272
+ return "string";
273
+ }
274
+ const lower = value.toLowerCase();
275
+ if (lower === "boolean" || lower === "int" || lower === "long" || lower === "float" || lower === "double") {
276
+ return lower;
277
+ }
278
+ return "string";
279
+ }
280
+ function parseEdgeDefault(value) {
281
+ if (typeof value !== "string") {
282
+ return false;
283
+ }
284
+ return value.toLowerCase() !== "undirected";
285
+ }
286
+ function parseDirected(value, defaultDirected) {
287
+ if (typeof value === "string") {
288
+ const normalized = value.trim().toLowerCase();
289
+ if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "directed") {
290
+ return true;
291
+ }
292
+ if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "undirected") {
293
+ return false;
294
+ }
295
+ }
296
+ return defaultDirected;
297
+ }
298
+ function parseBoolean(value) {
299
+ const normalized = value.trim().toLowerCase();
300
+ if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "y") {
301
+ return true;
302
+ }
303
+ if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "n") {
304
+ return false;
305
+ }
306
+ return Boolean(value);
307
+ }
308
+
309
+ // dist/graphml-loader.js
310
+ var GraphMLLoaderWithParser = {
311
+ ...GraphMLLoader,
312
+ parse: async (data) => parseGraphML(data),
313
+ parseSync: parseGraphML,
314
+ parseTextSync: parseGraphML
315
+ };
316
+
317
+ // dist/dot-loader-types.js
318
+ var VERSION2 = true ? "4.5.3" : "latest";
319
+ var DOTLoader = {
320
+ name: "DOT",
321
+ id: "dot",
322
+ module: "graphs",
323
+ version: VERSION2,
324
+ extensions: ["dot", "gv"],
325
+ mimeTypes: ["text/vnd.graphviz", "text/x-graphviz", "application/vnd.graphviz"],
326
+ text: true,
327
+ worker: false,
328
+ dataType: null,
329
+ batchType: null,
330
+ options: {}
331
+ };
332
+
333
+ // dist/lib/parse-dot.js
334
+ function parseDOT(input) {
335
+ const parsed = parseDOTDocument(input);
336
+ const subgraphs = Array.from(parsed.subgraphs.values(), (subgraph) => ({
337
+ ...subgraph,
338
+ attributes: { ...subgraph.attributes }
339
+ }));
340
+ return {
341
+ shape: "plain-graph-data",
342
+ nodes: Array.from(parsed.nodes.values(), (node) => ({
343
+ id: node.id,
344
+ ...typeof node.attributes.label === "string" ? { label: node.attributes.label } : {},
345
+ attributes: {
346
+ ...node.attributes,
347
+ ...node.subgraphs.size ? { subgraphs: Array.from(node.subgraphs, (id) => describeSubgraph(id, parsed.subgraphs)) } : {}
348
+ }
349
+ })),
350
+ edges: parsed.edges.map((edge) => ({
351
+ id: edge.id,
352
+ sourceId: edge.sourceId,
353
+ targetId: edge.targetId,
354
+ directed: edge.directed,
355
+ ...typeof edge.attributes.label === "string" ? { label: edge.attributes.label } : {},
356
+ attributes: {
357
+ ...edge.attributes,
358
+ ...edge.subgraphs.length ? { subgraphs: edge.subgraphs.map((id) => describeSubgraph(id, parsed.subgraphs)) } : {}
359
+ }
360
+ })),
361
+ metadata: {
362
+ id: parsed.id,
363
+ directed: parsed.directed,
364
+ strict: parsed.strict,
365
+ attributes: { ...parsed.graphAttributes },
366
+ subgraphs
367
+ }
368
+ };
369
+ }
370
+ function describeSubgraph(id, descriptors) {
371
+ const subgraph = descriptors.get(id);
372
+ return {
373
+ id,
374
+ attributes: { ...subgraph.attributes },
375
+ parentId: subgraph.parentId
376
+ };
377
+ }
378
+ var DOTParser = class {
379
+ /** Tokens for DOT parsing. */
380
+ tokens;
381
+ /** Position for DOT parsing. */
382
+ position = 0;
383
+ /** Accumulated graph records and attributes. */
384
+ result = {
385
+ directed: false,
386
+ strict: false,
387
+ graphAttributes: /* @__PURE__ */ Object.create(null),
388
+ nodes: /* @__PURE__ */ new Map(),
389
+ edges: [],
390
+ subgraphs: /* @__PURE__ */ new Map()
391
+ };
392
+ /** Scopes for DOT parsing. */
393
+ scopes = [
394
+ {
395
+ nodeDefaults: /* @__PURE__ */ Object.create(null),
396
+ edgeDefaults: /* @__PURE__ */ Object.create(null),
397
+ graphAttributes: /* @__PURE__ */ Object.create(null)
398
+ }
399
+ ];
400
+ /** Subgraph counter for DOT parsing. */
401
+ subgraphCounter = 0;
402
+ /** Edge counter for DOT parsing. */
403
+ edgeCounter = 0;
404
+ /** Edge lookup used to coalesce parallel edges in strict graphs. */
405
+ strictEdges = /* @__PURE__ */ new Map();
406
+ /** Persistent attributes and defaults for reopened named subgraphs. */
407
+ subgraphScopes = /* @__PURE__ */ new Map();
408
+ /** Explicit names reserved so anonymous subgraph IDs cannot collide with them. */
409
+ namedSubgraphIds = /* @__PURE__ */ new Set();
410
+ /** Initializes the token stream. */
411
+ constructor(tokens) {
412
+ this.tokens = tokens;
413
+ for (let index = 0; index < tokens.length - 1; index++) {
414
+ if (isKeyword(tokens[index], "subgraph") && isIdentifierLike(tokens[index + 1])) {
415
+ this.namedSubgraphIds.add(tokens[index + 1].value);
416
+ }
417
+ }
418
+ }
419
+ /** Parses one complete DOT document. */
420
+ parse() {
421
+ this.parseGraph();
422
+ this.consumeOptionalSemicolon();
423
+ if (this.peek()) {
424
+ throw new Error("Unexpected content after DOT graph.");
425
+ }
426
+ return this.result;
427
+ }
428
+ /** Reads graph identity, declaration, and statements. */
429
+ parseGraph() {
430
+ const strictToken = this.peek();
431
+ if (strictToken && isKeyword(strictToken, "strict")) {
432
+ this.consume();
433
+ this.result.strict = true;
434
+ }
435
+ const typeToken = this.peek();
436
+ if (!typeToken || !isGraphType(typeToken)) {
437
+ throw new Error("DOT graph must start with graph or digraph keyword.");
438
+ }
439
+ this.consume();
440
+ this.result.directed = isKeyword(typeToken, "digraph");
441
+ const idToken = this.peek();
442
+ if (idToken && isIdentifierLike(idToken)) {
443
+ this.result.id = parseIdentifierValue(this.consume());
444
+ }
445
+ this.expect("lbrace");
446
+ while (!this.match("rbrace")) {
447
+ if (!this.parseStatement()) {
448
+ throw new Error("Expected closing brace in DOT graph.");
449
+ }
450
+ }
451
+ this.result.graphAttributes = { ...this.scopes[0].graphAttributes };
452
+ }
453
+ /** Dispatches a graph statement. */
454
+ parseStatement() {
455
+ if (this.consumeSemicolonIfPresent()) {
456
+ return true;
457
+ }
458
+ const token = this.peek();
459
+ if (!token) {
460
+ return false;
461
+ }
462
+ if (this.tryParseSubgraphStatement(token)) {
463
+ return true;
464
+ }
465
+ if (this.tryParseKeywordStatement(token)) {
466
+ return true;
467
+ }
468
+ if (this.tryParseAssignmentStatement(token)) {
469
+ return true;
470
+ }
471
+ if (isIdentifierLike(token)) {
472
+ this.parseNodeOrEdgeStatement();
473
+ this.consumeOptionalSemicolon();
474
+ return true;
475
+ }
476
+ throw new Error(`Unexpected token: ${token.value}`);
477
+ }
478
+ /** Consumes an empty statement. */
479
+ consumeSemicolonIfPresent() {
480
+ const next = this.peek();
481
+ if ((next == null ? void 0 : next.type) === "semicolon") {
482
+ this.consume();
483
+ return true;
484
+ }
485
+ return false;
486
+ }
487
+ /** Reads a standalone subgraph statement. */
488
+ tryParseSubgraphStatement(token) {
489
+ if (token.type !== "lbrace" && !isKeyword(token, "subgraph")) {
490
+ return false;
491
+ }
492
+ this.parseSubgraph();
493
+ return true;
494
+ }
495
+ /** Applies graph, node, or edge defaults. */
496
+ tryParseKeywordStatement(token) {
497
+ if (!(isKeyword(token, "graph") || isKeyword(token, "node") || isKeyword(token, "edge"))) {
498
+ return false;
499
+ }
500
+ this.consume();
501
+ const explicitAttributes = this.parseAttributeList();
502
+ if (isKeyword(token, "graph")) {
503
+ Object.assign(this.currentScope().graphAttributes, explicitAttributes);
504
+ } else if (isKeyword(token, "node")) {
505
+ Object.assign(this.currentScope().nodeDefaults, explicitAttributes);
506
+ } else {
507
+ Object.assign(this.currentScope().edgeDefaults, explicitAttributes);
508
+ }
509
+ this.consumeOptionalSemicolon();
510
+ return true;
511
+ }
512
+ /** Reads a graph attribute assignment. */
513
+ tryParseAssignmentStatement(token) {
514
+ if (!isIdentifierLike(token)) {
515
+ return false;
516
+ }
517
+ const next = this.peek(1);
518
+ if ((next == null ? void 0 : next.type) !== "equals") {
519
+ return false;
520
+ }
521
+ const key = parseIdentifierValue(this.consume());
522
+ this.consume();
523
+ const valueToken = this.consume();
524
+ const value = parseAttributeValue(valueToken);
525
+ this.currentScope().graphAttributes[key] = value;
526
+ this.consumeOptionalSemicolon();
527
+ return true;
528
+ }
529
+ /** Reads a node statement or chained edges. */
530
+ parseNodeOrEdgeStatement() {
531
+ const first = parseIdentifierValue(this.consume());
532
+ const references = [first];
533
+ const operators = [];
534
+ let operatorToken = this.match("arrow");
535
+ while (operatorToken) {
536
+ operators.push(operatorToken.value);
537
+ const referenceToken = this.consume();
538
+ if (!isIdentifierLike(referenceToken)) {
539
+ throw new Error("Expected node identifier in edge statement.");
540
+ }
541
+ references.push(parseIdentifierValue(referenceToken));
542
+ operatorToken = this.match("arrow");
543
+ }
544
+ if (operators.length === 0) {
545
+ const explicitAttributes2 = this.parseAttributeList();
546
+ this.addNode(first, explicitAttributes2);
547
+ return;
548
+ }
549
+ const explicitAttributes = this.parseAttributeList();
550
+ this.addEdgeChain(references, operators, explicitAttributes);
551
+ }
552
+ /** Merges explicit attributes into a node. */
553
+ addNode(id, explicitAttributes) {
554
+ const membership = this.getCurrentSubgraphChain();
555
+ const node = this.ensureNode(id, membership);
556
+ node.attributes = { ...node.attributes, ...explicitAttributes };
557
+ }
558
+ /** Expands an edge chain into node and edge records. */
559
+ addEdgeChain(nodes, operators, explicitAttributes) {
560
+ const membership = this.getCurrentSubgraphChain();
561
+ const defaults = this.currentScope().edgeDefaults;
562
+ const attributes = { ...defaults, ...explicitAttributes };
563
+ for (let index = 0; index < nodes.length - 1; index++) {
564
+ const sourceId = nodes[index];
565
+ const targetId = nodes[index + 1];
566
+ const operator = operators[index];
567
+ const directed = operator === "->";
568
+ if (directed !== this.result.directed) {
569
+ throw new Error("DOT edge operator must match graph or digraph declaration.");
570
+ }
571
+ const edgeId = deriveEdgeId(attributes, sourceId, targetId, ++this.edgeCounter);
572
+ const edgeAttributes = { ...attributes };
573
+ this.ensureNode(sourceId, membership);
574
+ this.ensureNode(targetId, membership);
575
+ const strictKey = JSON.stringify(directed ? [sourceId, targetId] : [sourceId, targetId].sort());
576
+ const existingEdge = this.result.strict ? this.strictEdges.get(strictKey) : void 0;
577
+ if (existingEdge) {
578
+ existingEdge.attributes = { ...existingEdge.attributes, ...explicitAttributes };
579
+ existingEdge.directed = deriveDirectedFlag(existingEdge.attributes, directed);
580
+ existingEdge.subgraphs = Array.from(/* @__PURE__ */ new Set([...existingEdge.subgraphs, ...membership]));
581
+ continue;
582
+ }
583
+ const directedOverride = deriveDirectedFlag(edgeAttributes, directed);
584
+ const edge = {
585
+ id: edgeId,
586
+ sourceId,
587
+ targetId,
588
+ directed: directedOverride,
589
+ attributes: edgeAttributes,
590
+ subgraphs: membership
591
+ };
592
+ this.result.edges.push(edge);
593
+ if (this.result.strict) {
594
+ this.strictEdges.set(strictKey, edge);
595
+ }
596
+ }
597
+ }
598
+ /** Reads a nested scope with inherited defaults. */
599
+ parseSubgraph() {
600
+ let idToken = this.peek();
601
+ let subgraphId;
602
+ if (idToken && isKeyword(idToken, "subgraph")) {
603
+ this.consume();
604
+ idToken = this.peek();
605
+ }
606
+ if (idToken && isIdentifierLike(idToken)) {
607
+ subgraphId = parseIdentifierValue(this.consume());
608
+ } else {
609
+ do {
610
+ subgraphId = `subgraph_${++this.subgraphCounter}`;
611
+ } while (this.namedSubgraphIds.has(subgraphId));
612
+ }
613
+ this.expect("lbrace");
614
+ const parentId = this.findCurrentSubgraphId();
615
+ let context = this.subgraphScopes.get(subgraphId);
616
+ if (!context) {
617
+ context = {
618
+ id: subgraphId,
619
+ nodeDefaults: Object.assign(/* @__PURE__ */ Object.create(null), this.currentScope().nodeDefaults),
620
+ edgeDefaults: Object.assign(/* @__PURE__ */ Object.create(null), this.currentScope().edgeDefaults),
621
+ graphAttributes: /* @__PURE__ */ Object.create(null)
622
+ };
623
+ this.subgraphScopes.set(subgraphId, context);
624
+ this.result.subgraphs.set(subgraphId, {
625
+ id: subgraphId,
626
+ attributes: context.graphAttributes,
627
+ parentId
628
+ });
629
+ }
630
+ this.scopes.push(context);
631
+ let shouldContinue = true;
632
+ while (shouldContinue) {
633
+ if (this.match("rbrace")) {
634
+ break;
635
+ }
636
+ shouldContinue = this.parseStatement();
637
+ if (!shouldContinue) {
638
+ throw new Error("Expected closing brace in DOT subgraph.");
639
+ }
640
+ }
641
+ this.scopes.pop();
642
+ }
643
+ /** Reads repeated attribute lists. */
644
+ parseAttributeList() {
645
+ var _a, _b;
646
+ const attributes = /* @__PURE__ */ Object.create(null);
647
+ while (this.match("lbrack")) {
648
+ while (!this.match("rbrack")) {
649
+ const keyToken = this.consume();
650
+ if (!isIdentifierLike(keyToken)) {
651
+ throw new Error("Expected attribute name.");
652
+ }
653
+ const key = parseIdentifierValue(keyToken);
654
+ let value = true;
655
+ if (this.match("equals")) {
656
+ const valueToken = this.consume();
657
+ value = parseAttributeValue(valueToken);
658
+ }
659
+ attributes[key] = value;
660
+ if (((_a = this.peek()) == null ? void 0 : _a.type) === "comma" || ((_b = this.peek()) == null ? void 0 : _b.type) === "semicolon") {
661
+ this.consume();
662
+ }
663
+ }
664
+ }
665
+ return attributes;
666
+ }
667
+ /** Creates an implicit node and records its subgraph memberships. */
668
+ ensureNode(id, membership) {
669
+ let node = this.result.nodes.get(id);
670
+ if (!node) {
671
+ const defaults = this.currentScope().nodeDefaults;
672
+ node = { id, attributes: { ...defaults }, subgraphs: /* @__PURE__ */ new Set() };
673
+ this.result.nodes.set(id, node);
674
+ }
675
+ membership.forEach((subgraphId) => node.subgraphs.add(subgraphId));
676
+ return node;
677
+ }
678
+ /** Lists enclosing subgraphs. */
679
+ getCurrentSubgraphChain() {
680
+ const chain = [];
681
+ for (const scope of this.scopes) {
682
+ if (scope.id) {
683
+ chain.push(scope.id);
684
+ }
685
+ }
686
+ return chain;
687
+ }
688
+ /** Finds the nearest enclosing named or generated subgraph. */
689
+ findCurrentSubgraphId() {
690
+ for (let index = this.scopes.length - 1; index >= 0; index--) {
691
+ const scope = this.scopes[index];
692
+ if (scope.id) {
693
+ return scope.id;
694
+ }
695
+ }
696
+ return void 0;
697
+ }
698
+ /** Returns the active attribute scope. */
699
+ currentScope() {
700
+ return this.scopes[this.scopes.length - 1];
701
+ }
702
+ /** Consumes an optional statement terminator. */
703
+ consumeOptionalSemicolon() {
704
+ const next = this.peek();
705
+ if ((next == null ? void 0 : next.type) === "semicolon") {
706
+ this.consume();
707
+ }
708
+ }
709
+ /** Requires the next token to have the requested type. */
710
+ expect(type) {
711
+ const token = this.consume();
712
+ if (!token || token.type !== type) {
713
+ throw new Error(`Expected token ${type}.`);
714
+ }
715
+ return token;
716
+ }
717
+ /** Consumes the next token or reports truncated input. */
718
+ consume() {
719
+ const token = this.tokens[this.position];
720
+ if (!token) {
721
+ throw new Error("Unexpected end of DOT input.");
722
+ }
723
+ this.position++;
724
+ return token;
725
+ }
726
+ /** Consumes a token when its type matches. */
727
+ match(type) {
728
+ const token = this.peek();
729
+ if (token && token.type === type) {
730
+ this.position++;
731
+ return token;
732
+ }
733
+ return null;
734
+ }
735
+ /** Inspects a token without consuming it. */
736
+ peek(offset = 0) {
737
+ return this.tokens[this.position + offset] ?? null;
738
+ }
739
+ };
740
+ function parseDOTDocument(input) {
741
+ const parser = new DOTParser(tokenize(input));
742
+ return parser.parse();
743
+ }
744
+ var IDENTIFIER_TERMINATORS = /* @__PURE__ */ new Set(["{", "}", "[", "]", "=", ";", ",", '"', "<", "#"]);
745
+ function tokenize(input) {
746
+ const tokens = [];
747
+ let index = 0;
748
+ while (index < input.length) {
749
+ const { token, nextIndex } = readNextToken(input, index);
750
+ if (nextIndex <= index) {
751
+ throw new Error(`Tokenizer did not advance at position ${index}.`);
752
+ }
753
+ if (token) {
754
+ tokens.push(token);
755
+ }
756
+ index = nextIndex;
757
+ }
758
+ return tokens;
759
+ }
760
+ function readNextToken(input, index) {
761
+ if (index >= input.length) {
762
+ return { token: null, nextIndex: input.length };
763
+ }
764
+ const character = input[index];
765
+ if (isWhitespace(character)) {
766
+ return { token: null, nextIndex: index + 1 };
767
+ }
768
+ const commentEnd = skipComment(input, index);
769
+ if (commentEnd !== null) {
770
+ return { token: null, nextIndex: commentEnd };
771
+ }
772
+ const arrowToken = readArrowToken(input, index);
773
+ if (arrowToken) {
774
+ return arrowToken;
775
+ }
776
+ const punctuation = readPunctuationToken(character);
777
+ if (punctuation) {
778
+ return { token: punctuation, nextIndex: index + 1 };
779
+ }
780
+ if (character === '"') {
781
+ const { value, nextIndex } = readQuotedString(input, index + 1);
782
+ return { token: { type: "string", value }, nextIndex };
783
+ }
784
+ if (character === "<") {
785
+ const { value, nextIndex } = readHtmlString(input, index + 1);
786
+ return { token: { type: "html", value }, nextIndex };
787
+ }
788
+ const identifier = readIdentifier(input, index);
789
+ if (identifier.value) {
790
+ return {
791
+ token: { type: "identifier", value: identifier.value },
792
+ nextIndex: identifier.nextIndex
793
+ };
794
+ }
795
+ throw new Error(`Unexpected token at position ${index}.`);
796
+ }
797
+ function skipComment(input, index) {
798
+ const character = input[index];
799
+ if (character === "/") {
800
+ const next = input[index + 1];
801
+ if (next === "/") {
802
+ return skipLineComment(input, index + 2);
803
+ }
804
+ if (next === "*") {
805
+ return skipBlockComment(input, index + 2);
806
+ }
807
+ return null;
808
+ }
809
+ if (character === "#") {
810
+ return skipLineComment(input, index + 1);
811
+ }
812
+ return null;
813
+ }
814
+ function skipLineComment(input, startIndex) {
815
+ let cursor = startIndex;
816
+ while (cursor < input.length && !isLineBreak(input[cursor])) {
817
+ cursor++;
818
+ }
819
+ return cursor;
820
+ }
821
+ function skipBlockComment(input, startIndex) {
822
+ let cursor = startIndex;
823
+ while (cursor < input.length) {
824
+ if (input[cursor] === "*" && input[cursor + 1] === "/") {
825
+ return cursor + 2;
826
+ }
827
+ cursor++;
828
+ }
829
+ throw new Error("Unterminated block comment in DOT source.");
830
+ }
831
+ function readArrowToken(input, index) {
832
+ const next = input[index + 1];
833
+ if (input[index] === "-" && next === "-") {
834
+ return { token: { type: "arrow", value: "--" }, nextIndex: index + 2 };
835
+ }
836
+ if (input[index] === "-" && next === ">") {
837
+ return { token: { type: "arrow", value: "->" }, nextIndex: index + 2 };
838
+ }
839
+ return null;
840
+ }
841
+ function readPunctuationToken(character) {
842
+ switch (character) {
843
+ case "{":
844
+ return { type: "lbrace", value: character };
845
+ case "}":
846
+ return { type: "rbrace", value: character };
847
+ case "[":
848
+ return { type: "lbrack", value: character };
849
+ case "]":
850
+ return { type: "rbrack", value: character };
851
+ case "=":
852
+ return { type: "equals", value: character };
853
+ case ",":
854
+ return { type: "comma", value: character };
855
+ case ";":
856
+ return { type: "semicolon", value: character };
857
+ default:
858
+ return null;
859
+ }
860
+ }
861
+ function readQuotedString(input, startIndex) {
862
+ let value = "";
863
+ let index = startIndex;
864
+ while (index < input.length) {
865
+ const character = input[index];
866
+ if (character === '"') {
867
+ return { value, nextIndex: index + 1 };
868
+ }
869
+ if (character === "\\") {
870
+ const escape = readEscapedCharacter(input, index + 1);
871
+ value += escape.value;
872
+ index = escape.nextIndex;
873
+ } else {
874
+ value += character;
875
+ index++;
876
+ }
877
+ }
878
+ throw new Error("Unterminated string literal in DOT source.");
879
+ }
880
+ function readEscapedCharacter(input, startIndex) {
881
+ const next = input[startIndex];
882
+ switch (next) {
883
+ case "n":
884
+ case "l":
885
+ case "L":
886
+ return { value: "\n", nextIndex: startIndex + 1 };
887
+ case "t":
888
+ return { value: " ", nextIndex: startIndex + 1 };
889
+ case "r":
890
+ return { value: "\r", nextIndex: startIndex + 1 };
891
+ case '"':
892
+ return { value: '"', nextIndex: startIndex + 1 };
893
+ case "\\":
894
+ return { value: "\\", nextIndex: startIndex + 1 };
895
+ default: {
896
+ if (typeof next === "undefined") {
897
+ throw new Error("Unterminated escape sequence in DOT source.");
898
+ }
899
+ return { value: next, nextIndex: startIndex + 1 };
900
+ }
901
+ }
902
+ }
903
+ function readHtmlString(input, startIndex) {
904
+ let value = "<";
905
+ let depth = 1;
906
+ let index = startIndex;
907
+ while (index < input.length) {
908
+ const character = input[index];
909
+ value += character;
910
+ if (character === "<") {
911
+ depth++;
912
+ } else if (character === ">") {
913
+ depth--;
914
+ if (depth === 0) {
915
+ return { value, nextIndex: index + 1 };
916
+ }
917
+ }
918
+ index++;
919
+ }
920
+ throw new Error("Unterminated HTML-like string literal in DOT source.");
921
+ }
922
+ function readIdentifier(input, startIndex) {
923
+ let index = startIndex;
924
+ let value = "";
925
+ while (index < input.length && !isIdentifierTerminator(input, index)) {
926
+ value += input[index];
927
+ index++;
928
+ }
929
+ if (value.includes(":")) {
930
+ throw new Error("DOT node ports are not supported; quote identifiers containing colons.");
931
+ }
932
+ return { value, nextIndex: index };
933
+ }
934
+ function isIdentifierTerminator(input, index) {
935
+ const character = input[index];
936
+ if (isWhitespace(character) || IDENTIFIER_TERMINATORS.has(character)) {
937
+ return true;
938
+ }
939
+ if (isArrowOperatorStart(input, index)) {
940
+ return true;
941
+ }
942
+ if (isCommentStart(input, index)) {
943
+ return true;
944
+ }
945
+ return false;
946
+ }
947
+ function isArrowOperatorStart(input, index) {
948
+ if (input[index] !== "-") {
949
+ return false;
950
+ }
951
+ const next = input[index + 1];
952
+ return next === "-" || next === ">";
953
+ }
954
+ function isCommentStart(input, index) {
955
+ if (input[index] !== "/") {
956
+ return false;
957
+ }
958
+ const next = input[index + 1];
959
+ return next === "/" || next === "*";
960
+ }
961
+ function isWhitespace(character) {
962
+ return character === " " || character === "\n" || character === "\r" || character === " " || character === "\f";
963
+ }
964
+ function isLineBreak(character) {
965
+ return character === "\n" || character === "\r";
966
+ }
967
+ function isKeyword(token, keyword) {
968
+ return token.type === "identifier" && token.value.toLowerCase() === keyword.toLowerCase();
969
+ }
970
+ function isGraphType(token) {
971
+ return isKeyword(token, "graph") || isKeyword(token, "digraph");
972
+ }
973
+ function isIdentifierLike(token) {
974
+ return token.type === "identifier" || token.type === "string" || token.type === "html";
975
+ }
976
+ function parseIdentifierValue(token) {
977
+ return token.value;
978
+ }
979
+ function parseAttributeValue(token) {
980
+ if (token.type === "string" || token.type === "html") {
981
+ return token.value;
982
+ }
983
+ if (token.type === "identifier") {
984
+ const numeric = Number(token.value);
985
+ if (!Number.isNaN(numeric)) {
986
+ return numeric;
987
+ }
988
+ return token.value;
989
+ }
990
+ throw new Error("Invalid attribute value in DOT input.");
991
+ }
992
+ function deriveEdgeId(attributes, sourceId, targetId, counter) {
993
+ const candidate = attributes.id ?? attributes.Id ?? attributes.ID;
994
+ if (typeof candidate === "string" || typeof candidate === "number") {
995
+ return String(candidate);
996
+ }
997
+ return `${String(sourceId)}-${String(targetId)}-${counter}`;
998
+ }
999
+ function deriveDirectedFlag(attributes, defaultDirected) {
1000
+ const candidate = attributes.directed;
1001
+ if (typeof candidate === "boolean") {
1002
+ return candidate;
1003
+ }
1004
+ const directionAttribute = attributes.dir;
1005
+ if (typeof directionAttribute === "string") {
1006
+ const normalized = directionAttribute.toLowerCase();
1007
+ if (normalized === "none") {
1008
+ return false;
1009
+ }
1010
+ return true;
1011
+ }
1012
+ return defaultDirected;
1013
+ }
1014
+
1015
+ // dist/dot-loader.js
1016
+ var DOTLoaderWithParser = {
1017
+ ...DOTLoader,
1018
+ parse: async (data) => parseDOT(new TextDecoder().decode(data)),
1019
+ parseSync: (data) => parseDOT(new TextDecoder().decode(data)),
1020
+ parseTextSync: parseDOT
1021
+ };
1022
+ //# sourceMappingURL=index.cjs.map