keepset 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { $ as ArchiveError, H as getCollectionTarget, L as getOwn, U as getTarget, at as RecipeError, it as NativeParseError, rt as KeepsetError, t as getDataset, z as countTarget } from "../bundled-DV_kxyz2.js";
1
+ import { $ as ArchiveError, H as getCollectionTarget, L as getOwn, U as getTarget, at as RecipeError, it as NativeParseError, rt as KeepsetError, t as getDataset, z as countTarget } from "../bundled-3xBd8sP5.js";
2
2
  import { RoundTripMismatchError, assertRoundTrip, encode } from "crossmap";
3
3
  import { z } from "zod";
4
4
  import { createHash } from "node:crypto";
@@ -102,9 +102,61 @@ function parseEdgeList(text, options) {
102
102
  };
103
103
  }
104
104
  //#endregion
105
+ //#region src/parsers/gd-contest.ts
106
+ const FORMAT$8 = "gd-contest";
107
+ const NUMBER$8 = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?$/;
108
+ /**
109
+ * Reads a vertex number, which the format numbers from 0 to N - 1.
110
+ * @param token - The token.
111
+ * @param count - The declared vertex count.
112
+ * @returns The vertex id as text.
113
+ */
114
+ function vertex$1(token, count) {
115
+ if (!/^(?:0|[1-9]\d*)$/.test(token.text) || Number(token.text) >= count) throw new NativeParseError(FORMAT$8, token.line, `expected a vertex number from 0 to ${String(count - 1)}, found ${JSON.stringify(token.text)}`);
116
+ return token.text;
117
+ }
118
+ /**
119
+ * Parses a Graph Drawing contest file (the `.gd4` format of the GD 2004 contest rules, http://vlado.fmf.uni-lj.si/pub/networks/data/GD/gd04/gd04.zip, `GD04/rules04.htm`) as a graph. The file is a sequence of values, with `#` starting a comment that runs to the end of its line: the vertex count N, then an X and a Y coordinate for each vertex in turn, then pairs of vertex numbers, one pair per undirected edge. Nodes are `0` to `N - 1`, isolated ones included, and edges are the pairs in file order. The coordinates are not carried: the rules give them as random starting positions for the contestants to move, not as data about the graph.
120
+ * @param text - The file text.
121
+ * @returns The native graph.
122
+ * @throws NativeParseError naming the line for a missing or malformed vertex count, a coordinate that is not a number, too few coordinates, a vertex number outside 0 to N - 1, or an edge missing its second vertex.
123
+ */
124
+ function parseGdContest(text) {
125
+ const tokens = [];
126
+ for (const [index, raw] of lines(text).entries()) {
127
+ const comment = raw.indexOf("#");
128
+ const content = comment === -1 ? raw : raw.slice(0, comment);
129
+ for (const token of content.split(/\s+/).filter((part) => part !== "")) tokens.push({
130
+ text: token,
131
+ line: index + 1
132
+ });
133
+ }
134
+ const [first] = tokens;
135
+ if (first === void 0) throw new NativeParseError(FORMAT$8, 1, "the file holds no vertex count");
136
+ if (!/^(?:0|[1-9]\d*)$/.test(first.text)) throw new NativeParseError(FORMAT$8, first.line, `the vertex count must be a non-negative integer, found ${JSON.stringify(first.text)}`);
137
+ const count = Number(first.text);
138
+ const coordinates = tokens.slice(1, 1 + 2 * count);
139
+ if (coordinates.length < 2 * count) throw new NativeParseError(FORMAT$8, lines(text).length, `expected ${String(2 * count)} coordinates for ${String(count)} vertices, found ${String(coordinates.length)}`);
140
+ for (const coordinate of coordinates) if (!NUMBER$8.test(coordinate.text)) throw new NativeParseError(FORMAT$8, coordinate.line, `coordinate ${JSON.stringify(coordinate.text)} is not a number`);
141
+ const ends = tokens.slice(1 + 2 * count);
142
+ const edges = [];
143
+ for (let source = ends.shift(); source !== void 0; source = ends.shift()) {
144
+ const target = ends.shift();
145
+ if (target === void 0) throw new NativeParseError(FORMAT$8, source.line, `edge from vertex ${source.text} has no second vertex`);
146
+ edges.push({
147
+ source: vertex$1(source, count),
148
+ target: vertex$1(target, count)
149
+ });
150
+ }
151
+ return {
152
+ nodes: Array.from({ length: count }, (_, id) => ({ id: String(id) })),
153
+ edges
154
+ };
155
+ }
156
+ //#endregion
105
157
  //#region src/parsers/gml.ts
106
158
  const KEY = /[A-Za-z_][A-Za-z0-9_]*/y;
107
- const NUMBER$6 = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?/y;
159
+ const NUMBER$7 = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?/y;
108
160
  /**
109
161
  * Matches a sticky pattern at a position.
110
162
  * @param pattern - A sticky regular expression.
@@ -155,7 +207,7 @@ function tokenise(text) {
155
207
  position = end + 1;
156
208
  } else {
157
209
  const key = matchAt(KEY, text, position);
158
- const number = key === void 0 ? matchAt(NUMBER$6, text, position) : void 0;
210
+ const number = key === void 0 ? matchAt(NUMBER$7, text, position) : void 0;
159
211
  const match = key ?? number;
160
212
  if (match === void 0) throw new NativeParseError("gml", line, `unexpected character ${JSON.stringify(char)}`);
161
213
  tokens.push({
@@ -833,8 +885,8 @@ function parsePajek(text) {
833
885
  }
834
886
  //#endregion
835
887
  //#region src/parsers/customer-points.ts
836
- const FORMAT$6 = "customer-points";
837
- const NUMBER$5 = /^-?\d+(?:\.\d+)?$/;
888
+ const FORMAT$7 = "customer-points";
889
+ const NUMBER$6 = /^-?\d+(?:\.\d+)?$/;
838
890
  const WHOLE$2 = /^\d+$/;
839
891
  /**
840
892
  * Parses a customer points file, as the OAR Bench arc routing benchmark generator writes the customers of an instance: the line `customers <count>`, then one row per customer of its id, x and y, separated by tabs or spaces, with blank lines skipped. The positions are planar coordinates in the instance's own unit, which the file does not name. The row count must be the announced count, which catches a truncated file.
@@ -849,12 +901,12 @@ function parseCustomerPoints(text) {
849
901
  })).filter((row) => row.fields.length > 0);
850
902
  const [first, ...body] = rows;
851
903
  const [key, count, ...extra] = first?.fields ?? [];
852
- if (first === void 0 || key !== "customers" || count === void 0 || !WHOLE$2.test(count) || extra.length > 0) throw new NativeParseError(FORMAT$6, first?.line ?? 1, `expected "customers <count>", found ${JSON.stringify(first?.fields.join(" "))}`);
904
+ if (first === void 0 || key !== "customers" || count === void 0 || !WHOLE$2.test(count) || extra.length > 0) throw new NativeParseError(FORMAT$7, first?.line ?? 1, `expected "customers <count>", found ${JSON.stringify(first?.fields.join(" "))}`);
853
905
  const seen = /* @__PURE__ */ new Set();
854
906
  const nodes = body.map((row) => {
855
907
  const [id, x, y, ...rest] = row.fields;
856
- if (id === void 0 || x === void 0 || y === void 0 || rest.length > 0 || !WHOLE$2.test(id) || !NUMBER$5.test(x) || !NUMBER$5.test(y)) throw new NativeParseError(FORMAT$6, row.line, `expected a customer id and two numbers, found ${JSON.stringify(row.fields.join(" "))}`);
857
- if (seen.has(id)) throw new NativeParseError(FORMAT$6, row.line, `customer ${id} appears twice`);
908
+ if (id === void 0 || x === void 0 || y === void 0 || rest.length > 0 || !WHOLE$2.test(id) || !NUMBER$6.test(x) || !NUMBER$6.test(y)) throw new NativeParseError(FORMAT$7, row.line, `expected a customer id and two numbers, found ${JSON.stringify(row.fields.join(" "))}`);
909
+ if (seen.has(id)) throw new NativeParseError(FORMAT$7, row.line, `customer ${id} appears twice`);
858
910
  seen.add(id);
859
911
  return {
860
912
  id,
@@ -862,7 +914,7 @@ function parseCustomerPoints(text) {
862
914
  y: Number(y)
863
915
  };
864
916
  });
865
- if (nodes.length !== Number(count)) throw new NativeParseError(FORMAT$6, rows[rows.length - 1]?.line ?? first.line, `the file announces ${count} customers, found ${String(nodes.length)}`);
917
+ if (nodes.length !== Number(count)) throw new NativeParseError(FORMAT$7, rows[rows.length - 1]?.line ?? first.line, `the file announces ${count} customers, found ${String(nodes.length)}`);
866
918
  return {
867
919
  meta: { customers: Number(count) },
868
920
  nodes
@@ -870,9 +922,9 @@ function parseCustomerPoints(text) {
870
922
  }
871
923
  //#endregion
872
924
  //#region src/parsers/solomon.ts
873
- const FORMAT$5 = "solomon";
925
+ const FORMAT$6 = "solomon";
874
926
  /** A line of integers or decimals separated by any run of spaces and tabs, as the files write rows (Solomon's with padded spaces, the Tenerife set with tabs). */
875
- const NUMBER$4 = /^-?\d+(?:\.\d+)?$/;
927
+ const NUMBER$5 = /^-?\d+(?:\.\d+)?$/;
876
928
  /**
877
929
  * Reads a token as a finite number.
878
930
  * @param token - The token.
@@ -882,7 +934,7 @@ const NUMBER$4 = /^-?\d+(?:\.\d+)?$/;
882
934
  * @throws NativeParseError when the token is not a decimal number.
883
935
  */
884
936
  function numberOf$3(token, line, what) {
885
- if (!NUMBER$4.test(token)) throw new NativeParseError(FORMAT$5, line, `expected ${what}, found ${JSON.stringify(token)}`);
937
+ if (!NUMBER$5.test(token)) throw new NativeParseError(FORMAT$6, line, `expected ${what}, found ${JSON.stringify(token)}`);
886
938
  return Number(token);
887
939
  }
888
940
  /**
@@ -895,7 +947,7 @@ function parseSolomon(text) {
895
947
  const all = lines(text);
896
948
  const present = (index) => (all[index] ?? "").trim();
897
949
  const name = present(0);
898
- if (name === "") throw new NativeParseError(FORMAT$5, 1, "expected the instance name on the first line");
950
+ if (name === "") throw new NativeParseError(FORMAT$6, 1, "expected the instance name on the first line");
899
951
  const meta = { name };
900
952
  const nodes = [];
901
953
  const seen = /* @__PURE__ */ new Set();
@@ -911,7 +963,7 @@ function parseSolomon(text) {
911
963
  awaitingHeader = true;
912
964
  continue;
913
965
  }
914
- if (section === "preamble") throw new NativeParseError(FORMAT$5, line, `expected VEHICLE or CUSTOMER, found ${JSON.stringify(trimmed)}`);
966
+ if (section === "preamble") throw new NativeParseError(FORMAT$6, line, `expected VEHICLE or CUSTOMER, found ${JSON.stringify(trimmed)}`);
915
967
  if (awaitingHeader) {
916
968
  awaitingHeader = false;
917
969
  continue;
@@ -919,15 +971,15 @@ function parseSolomon(text) {
919
971
  const tokens = trimmed.split(/\s+/);
920
972
  if (section === "vehicle") {
921
973
  const [vehicles, capacity, ...extra] = tokens;
922
- if (vehicles === void 0 || capacity === void 0 || extra.length > 0) throw new NativeParseError(FORMAT$5, line, `expected the vehicle count and capacity, found ${JSON.stringify(trimmed)}`);
974
+ if (vehicles === void 0 || capacity === void 0 || extra.length > 0) throw new NativeParseError(FORMAT$6, line, `expected the vehicle count and capacity, found ${JSON.stringify(trimmed)}`);
923
975
  meta.vehicles = numberOf$3(vehicles, line, "the vehicle count");
924
976
  meta.capacity = numberOf$3(capacity, line, "the vehicle capacity");
925
977
  continue;
926
978
  }
927
979
  const [id, x, y, demand, ready, due, service, ...extra] = tokens;
928
- if (id === void 0 || x === void 0 || y === void 0 || demand === void 0 || ready === void 0 || due === void 0 || service === void 0 || extra.length > 0) throw new NativeParseError(FORMAT$5, line, `expected seven numbers (id, x, y, demand, ready, due, service), found ${JSON.stringify(trimmed)}`);
929
- if (!/^\d+$/.test(id)) throw new NativeParseError(FORMAT$5, line, `expected a whole number node id, found ${JSON.stringify(id)}`);
930
- if (seen.has(id)) throw new NativeParseError(FORMAT$5, line, `node ${id} appears twice`);
980
+ if (id === void 0 || x === void 0 || y === void 0 || demand === void 0 || ready === void 0 || due === void 0 || service === void 0 || extra.length > 0) throw new NativeParseError(FORMAT$6, line, `expected seven numbers (id, x, y, demand, ready, due, service), found ${JSON.stringify(trimmed)}`);
981
+ if (!/^\d+$/.test(id)) throw new NativeParseError(FORMAT$6, line, `expected a whole number node id, found ${JSON.stringify(id)}`);
982
+ if (seen.has(id)) throw new NativeParseError(FORMAT$6, line, `node ${id} appears twice`);
931
983
  seen.add(id);
932
984
  nodes.push({
933
985
  id,
@@ -939,9 +991,9 @@ function parseSolomon(text) {
939
991
  service: numberOf$3(service, line, "the service time")
940
992
  });
941
993
  }
942
- if (section !== "customer") throw new NativeParseError(FORMAT$5, all.length, "expected a CUSTOMER section");
943
- if (nodes.length === 0) throw new NativeParseError(FORMAT$5, all.length, "the CUSTOMER section has no rows");
944
- if (meta.vehicles === void 0 || meta.capacity === void 0) throw new NativeParseError(FORMAT$5, all.length, "expected a VEHICLE section with the vehicle count and capacity");
994
+ if (section !== "customer") throw new NativeParseError(FORMAT$6, all.length, "expected a CUSTOMER section");
995
+ if (nodes.length === 0) throw new NativeParseError(FORMAT$6, all.length, "the CUSTOMER section has no rows");
996
+ if (meta.vehicles === void 0 || meta.capacity === void 0) throw new NativeParseError(FORMAT$6, all.length, "expected a VEHICLE section with the vehicle count and capacity");
945
997
  return {
946
998
  meta: {
947
999
  name: meta.name,
@@ -953,10 +1005,10 @@ function parseSolomon(text) {
953
1005
  }
954
1006
  //#endregion
955
1007
  //#region src/parsers/top.ts
956
- const FORMAT$4 = "top";
1008
+ const FORMAT$5 = "top";
957
1009
  /** The header lines before the first point: `n`, `m` and `tmax`. */
958
1010
  const HEADER_LINES = 3;
959
- const NUMBER$3 = /^-?\d+(?:\.\d+)?$/;
1011
+ const NUMBER$4 = /^-?\d+(?:\.\d+)?$/;
960
1012
  /**
961
1013
  * Reads the value of a header line `<key> <number>`.
962
1014
  * @param text - The line.
@@ -967,7 +1019,7 @@ const NUMBER$3 = /^-?\d+(?:\.\d+)?$/;
967
1019
  */
968
1020
  function headerOf(text, key, line) {
969
1021
  const [name, value, ...extra] = (text ?? "").trim().split(/\s+/);
970
- if (name !== key || value === void 0 || extra.length > 0 || !NUMBER$3.test(value)) throw new NativeParseError(FORMAT$4, line, `expected "${key} <number>", found ${JSON.stringify((text ?? "").trim())}`);
1022
+ if (name !== key || value === void 0 || extra.length > 0 || !NUMBER$4.test(value)) throw new NativeParseError(FORMAT$5, line, `expected "${key} <number>", found ${JSON.stringify((text ?? "").trim())}`);
971
1023
  return Number(value);
972
1024
  }
973
1025
  /**
@@ -986,11 +1038,11 @@ function parseTop(text) {
986
1038
  const points = headerOf(first?.text, "n", first?.line ?? 1);
987
1039
  const paths = headerOf(second?.text, "m", second?.line ?? 2);
988
1040
  const budget = headerOf(third?.text, "tmax", third?.line ?? HEADER_LINES);
989
- if (!Number.isInteger(points) || points < 2) throw new NativeParseError(FORMAT$4, first?.line ?? 1, `expected at least a start and an end point, found n ${String(points)}`);
990
- if (rows.length !== points) throw new NativeParseError(FORMAT$4, all[all.length - 1]?.line ?? 1, `the header gives ${String(points)} points, found ${String(rows.length)} rows`);
1041
+ if (!Number.isInteger(points) || points < 2) throw new NativeParseError(FORMAT$5, first?.line ?? 1, `expected at least a start and an end point, found n ${String(points)}`);
1042
+ if (rows.length !== points) throw new NativeParseError(FORMAT$5, all[all.length - 1]?.line ?? 1, `the header gives ${String(points)} points, found ${String(rows.length)} rows`);
991
1043
  const nodes = rows.map((row, index) => {
992
1044
  const [x, y, score, ...extra] = row.text.split(/\s+/);
993
- if (x === void 0 || y === void 0 || score === void 0 || extra.length > 0 || !NUMBER$3.test(x) || !NUMBER$3.test(y) || !NUMBER$3.test(score)) throw new NativeParseError(FORMAT$4, row.line, `expected three numbers (x, y, score), found ${JSON.stringify(row.text)}`);
1045
+ if (x === void 0 || y === void 0 || score === void 0 || extra.length > 0 || !NUMBER$4.test(x) || !NUMBER$4.test(y) || !NUMBER$4.test(score)) throw new NativeParseError(FORMAT$5, row.line, `expected three numbers (x, y, score), found ${JSON.stringify(row.text)}`);
994
1046
  return {
995
1047
  id: String(index),
996
1048
  x: Number(x),
@@ -1010,7 +1062,7 @@ function parseTop(text) {
1010
1062
  }
1011
1063
  //#endregion
1012
1064
  //#region src/parsers/toptw.ts
1013
- const FORMAT$3 = "toptw";
1065
+ const FORMAT$4 = "toptw";
1014
1066
  /** How many numbers the first header line holds (k, paths, customers, t) and the second (D, capacity). */
1015
1067
  const FIRST_HEADER_NUMBERS = 4;
1016
1068
  const SECOND_HEADER_NUMBERS = 2;
@@ -1018,7 +1070,7 @@ const SECOND_HEADER_NUMBERS = 2;
1018
1070
  const LIST_LENGTH_AT = 6;
1019
1071
  const LIST_STARTS_AT = 7;
1020
1072
  const ROW_NUMBERS_BESIDE_LIST = 9;
1021
- const NUMBER$2 = /^-?\d+(?:\.\d+)?$/;
1073
+ const NUMBER$3 = /^-?\d+(?:\.\d+)?$/;
1022
1074
  /**
1023
1075
  * Reads a token as a finite number.
1024
1076
  * @param token - The token.
@@ -1028,7 +1080,7 @@ const NUMBER$2 = /^-?\d+(?:\.\d+)?$/;
1028
1080
  * @throws NativeParseError when the token is not a decimal number.
1029
1081
  */
1030
1082
  function numberOf$2(token, line, what) {
1031
- if (token === void 0 || !NUMBER$2.test(token)) throw new NativeParseError(FORMAT$3, line, `expected ${what}, found ${JSON.stringify(token)}`);
1083
+ if (token === void 0 || !NUMBER$3.test(token)) throw new NativeParseError(FORMAT$4, line, `expected ${what}, found ${JSON.stringify(token)}`);
1032
1084
  return Number(token);
1033
1085
  }
1034
1086
  /**
@@ -1042,7 +1094,7 @@ function numberOf$2(token, line, what) {
1042
1094
  */
1043
1095
  function numbersOf(text, count, line, what) {
1044
1096
  const tokens = text.trim().split(/\s+/);
1045
- if (tokens.length !== count) throw new NativeParseError(FORMAT$3, line, `expected ${what}, found ${JSON.stringify(text.trim())}`);
1097
+ if (tokens.length !== count) throw new NativeParseError(FORMAT$4, line, `expected ${what}, found ${JSON.stringify(text.trim())}`);
1046
1098
  return tokens.map((token) => numberOf$2(token, line, what));
1047
1099
  }
1048
1100
  /**
@@ -1057,18 +1109,18 @@ function parseToptw(text) {
1057
1109
  text: raw.trim()
1058
1110
  })).filter((entry) => entry.text !== "");
1059
1111
  const [first, second, ...rows] = all;
1060
- if (first === void 0 || second === void 0) throw new NativeParseError(FORMAT$3, 1, "expected the two header lines");
1112
+ if (first === void 0 || second === void 0) throw new NativeParseError(FORMAT$4, 1, "expected the two header lines");
1061
1113
  const [, paths, customers] = numbersOf(first.text, FIRST_HEADER_NUMBERS, first.line, "four numbers (k, paths, customers, t)");
1062
1114
  const [, capacity] = numbersOf(second.text, SECOND_HEADER_NUMBERS, second.line, "two numbers (D, capacity)");
1063
- if (paths === void 0 || customers === void 0 || capacity === void 0) throw new NativeParseError(FORMAT$3, first.line, "the header lacks a number");
1115
+ if (paths === void 0 || customers === void 0 || capacity === void 0) throw new NativeParseError(FORMAT$4, first.line, "the header lacks a number");
1064
1116
  const seen = /* @__PURE__ */ new Set();
1065
1117
  const nodes = rows.map((row) => {
1066
1118
  const tokens = row.text.split(/\s+/);
1067
1119
  const listLength = numberOf$2(tokens[LIST_LENGTH_AT], row.line, "the length of the list after the unused number");
1068
- if (!Number.isInteger(listLength) || listLength < 0 || tokens.length !== ROW_NUMBERS_BESIDE_LIST + listLength) throw new NativeParseError(FORMAT$3, row.line, `expected ${String(ROW_NUMBERS_BESIDE_LIST + listLength)} numbers for a list of ${String(listLength)}, found ${String(tokens.length)}`);
1120
+ if (!Number.isInteger(listLength) || listLength < 0 || tokens.length !== ROW_NUMBERS_BESIDE_LIST + listLength) throw new NativeParseError(FORMAT$4, row.line, `expected ${String(ROW_NUMBERS_BESIDE_LIST + listLength)} numbers for a list of ${String(listLength)}, found ${String(tokens.length)}`);
1069
1121
  const [id] = tokens;
1070
- if (id === void 0 || !/^\d+$/.test(id)) throw new NativeParseError(FORMAT$3, row.line, `expected a whole number node id, found ${JSON.stringify(id)}`);
1071
- if (seen.has(id)) throw new NativeParseError(FORMAT$3, row.line, `node ${id} appears twice`);
1122
+ if (id === void 0 || !/^\d+$/.test(id)) throw new NativeParseError(FORMAT$4, row.line, `expected a whole number node id, found ${JSON.stringify(id)}`);
1123
+ if (seen.has(id)) throw new NativeParseError(FORMAT$4, row.line, `node ${id} appears twice`);
1072
1124
  seen.add(id);
1073
1125
  return {
1074
1126
  id,
@@ -1080,7 +1132,7 @@ function parseToptw(text) {
1080
1132
  close: numberOf$2(tokens[8 + listLength], row.line, "the closing of the window")
1081
1133
  };
1082
1134
  });
1083
- if (nodes.length !== customers + 1) throw new NativeParseError(FORMAT$3, all.length === 0 ? 1 : all[all.length - 1]?.line ?? 1, `the header gives ${String(customers)} customers, so expected ${String(customers + 1)} rows with the depot, found ${String(nodes.length)}`);
1135
+ if (nodes.length !== customers + 1) throw new NativeParseError(FORMAT$4, all.length === 0 ? 1 : all[all.length - 1]?.line ?? 1, `the header gives ${String(customers)} customers, so expected ${String(customers + 1)} rows with the depot, found ${String(nodes.length)}`);
1084
1136
  return {
1085
1137
  meta: {
1086
1138
  paths,
@@ -1092,7 +1144,7 @@ function parseToptw(text) {
1092
1144
  }
1093
1145
  //#endregion
1094
1146
  //#region src/parsers/tsplib.ts
1095
- const FORMAT$2 = "tsplib";
1147
+ const FORMAT$3 = "tsplib";
1096
1148
  /** The instance types the parser reads: the probabilistic and the clustered orienteering problem. */
1097
1149
  const TYPES = /* @__PURE__ */ new Set(["POP", "COP"]);
1098
1150
  /** The edge weight types a coordinate section is read under: planar ones keep their coordinates as `x` and `y`, `GEO` converts them to degrees, and `EXPLICIT` has no coordinates. */
@@ -1101,7 +1153,7 @@ const PLANAR = /* @__PURE__ */ new Set([
1101
1153
  "CEIL_2D",
1102
1154
  "ATT"
1103
1155
  ]);
1104
- const NUMBER$1 = /^-?\d+(?:\.\d+)?$/;
1156
+ const NUMBER$2 = /^-?\d+(?:\.\d+)?$/;
1105
1157
  const WHOLE$1 = /^\d+$/;
1106
1158
  const HEADER$2 = /^([A-Z][A-Z_]*)\s*:\s*(.*)$/;
1107
1159
  const SECTION = /^[A-Z][A-Z_]*$/;
@@ -1123,7 +1175,7 @@ function degrees(value, line) {
1123
1175
  const whole = Math.trunc(magnitude);
1124
1176
  const hundredths = (magnitude - whole) * MINUTE_SCALE;
1125
1177
  const minutes = Math.round(hundredths);
1126
- if (Math.abs(hundredths - minutes) > MINUTE_TOLERANCE || minutes >= MINUTES_PER_DEGREE) throw new NativeParseError(FORMAT$2, line, `expected a DDD.MM coordinate with whole minutes below 60, found ${String(value)}`);
1178
+ if (Math.abs(hundredths - minutes) > MINUTE_TOLERANCE || minutes >= MINUTES_PER_DEGREE) throw new NativeParseError(FORMAT$3, line, `expected a DDD.MM coordinate with whole minutes below 60, found ${String(value)}`);
1127
1179
  return Math.sign(value) * (whole + minutes / MINUTES_PER_DEGREE);
1128
1180
  }
1129
1181
  /**
@@ -1135,7 +1187,7 @@ function degrees(value, line) {
1135
1187
  * @throws NativeParseError when the token is not a decimal number.
1136
1188
  */
1137
1189
  function numberOf$1(token, line, what) {
1138
- if (token === void 0 || !NUMBER$1.test(token)) throw new NativeParseError(FORMAT$2, line, `expected ${what}, found ${JSON.stringify(token)}`);
1190
+ if (token === void 0 || !NUMBER$2.test(token)) throw new NativeParseError(FORMAT$3, line, `expected ${what}, found ${JSON.stringify(token)}`);
1139
1191
  return Number(token);
1140
1192
  }
1141
1193
  /**
@@ -1147,7 +1199,7 @@ function numberOf$1(token, line, what) {
1147
1199
  * @throws NativeParseError when the token is not a whole number.
1148
1200
  */
1149
1201
  function wholeOf(token, line, what) {
1150
- if (token === void 0 || !WHOLE$1.test(token)) throw new NativeParseError(FORMAT$2, line, `expected ${what}, found ${JSON.stringify(token)}`);
1202
+ if (token === void 0 || !WHOLE$1.test(token)) throw new NativeParseError(FORMAT$3, line, `expected ${what}, found ${JSON.stringify(token)}`);
1151
1203
  return Number(token);
1152
1204
  }
1153
1205
  /**
@@ -1175,7 +1227,7 @@ function collect(text) {
1175
1227
  continue;
1176
1228
  }
1177
1229
  if (keyed !== null && key !== void 0) {
1178
- if (collected.header.has(key)) throw new NativeParseError(FORMAT$2, line, `${key} appears twice`);
1230
+ if (collected.header.has(key)) throw new NativeParseError(FORMAT$3, line, `${key} appears twice`);
1179
1231
  collected.header.set(key, (keyed[2] ?? "").trim());
1180
1232
  section = void 0;
1181
1233
  continue;
@@ -1187,9 +1239,9 @@ function collect(text) {
1187
1239
  const tokens = trimmed.split(/\s+/);
1188
1240
  if (section === "coords") {
1189
1241
  const [id, x, y, ...extra] = tokens;
1190
- if (extra.length > 0) throw new NativeParseError(FORMAT$2, line, `expected "id x y", found ${JSON.stringify(trimmed)}`);
1242
+ if (extra.length > 0) throw new NativeParseError(FORMAT$3, line, `expected "id x y", found ${JSON.stringify(trimmed)}`);
1191
1243
  const node = wholeOf(id, line, "a node id");
1192
- if (collected.coords.has(node)) throw new NativeParseError(FORMAT$2, line, `node ${String(node)} has two coordinate rows`);
1244
+ if (collected.coords.has(node)) throw new NativeParseError(FORMAT$3, line, `node ${String(node)} has two coordinate rows`);
1193
1245
  collected.coords.set(node, {
1194
1246
  x: numberOf$1(x, line, "x"),
1195
1247
  y: numberOf$1(y, line, "y"),
@@ -1197,9 +1249,9 @@ function collect(text) {
1197
1249
  });
1198
1250
  } else if (section === "prizes") {
1199
1251
  const [id, prize, probability, ...extra] = tokens;
1200
- if (extra.length > 0) throw new NativeParseError(FORMAT$2, line, `expected "id prize probability", found ${JSON.stringify(trimmed)}`);
1252
+ if (extra.length > 0) throw new NativeParseError(FORMAT$3, line, `expected "id prize probability", found ${JSON.stringify(trimmed)}`);
1201
1253
  const node = wholeOf(id, line, "a node id");
1202
- if (collected.prizes.has(node)) throw new NativeParseError(FORMAT$2, line, `node ${String(node)} has two prize rows`);
1254
+ if (collected.prizes.has(node)) throw new NativeParseError(FORMAT$3, line, `node ${String(node)} has two prize rows`);
1203
1255
  collected.prizes.set(node, {
1204
1256
  prize: numberOf$1(prize, line, "the prize"),
1205
1257
  probability: numberOf$1(probability, line, "the probability")
@@ -1207,13 +1259,13 @@ function collect(text) {
1207
1259
  } else if (section === "sets") {
1208
1260
  const [id, prize, ...members] = tokens;
1209
1261
  const setId = wholeOf(id, line, "a set id");
1210
- if (collected.sets.some((set) => set.id === setId)) throw new NativeParseError(FORMAT$2, line, `set ${String(setId)} appears twice`);
1262
+ if (collected.sets.some((set) => set.id === setId)) throw new NativeParseError(FORMAT$3, line, `set ${String(setId)} appears twice`);
1211
1263
  collected.sets.push({
1212
1264
  id: setId,
1213
1265
  prize: numberOf$1(prize, line, "the set prize"),
1214
1266
  members: members.map((member) => wholeOf(member, line, "a node id in a set"))
1215
1267
  });
1216
- } else if (section === void 0) throw new NativeParseError(FORMAT$2, line, `expected a KEY : value line or a section, found ${JSON.stringify(trimmed)}`);
1268
+ } else if (section === void 0) throw new NativeParseError(FORMAT$3, line, `expected a KEY : value line or a section, found ${JSON.stringify(trimmed)}`);
1217
1269
  }
1218
1270
  return collected;
1219
1271
  }
@@ -1226,7 +1278,7 @@ function collect(text) {
1226
1278
  */
1227
1279
  function required(header, key) {
1228
1280
  const value = header.get(key);
1229
- if (value === void 0) throw new NativeParseError(FORMAT$2, 1, `expected a ${key} line`);
1281
+ if (value === void 0) throw new NativeParseError(FORMAT$3, 1, `expected a ${key} line`);
1230
1282
  return value;
1231
1283
  }
1232
1284
  /**
@@ -1238,23 +1290,23 @@ function required(header, key) {
1238
1290
  function parseTsplib(text) {
1239
1291
  const { header, coords, prizes, sets } = collect(text);
1240
1292
  const type = required(header, "TYPE");
1241
- if (!TYPES.has(type)) throw new NativeParseError(FORMAT$2, 1, `expected TYPE POP or COP, found ${JSON.stringify(type)}`);
1293
+ if (!TYPES.has(type)) throw new NativeParseError(FORMAT$3, 1, `expected TYPE POP or COP, found ${JSON.stringify(type)}`);
1242
1294
  const weightType = required(header, "EDGE_WEIGHT_TYPE");
1243
1295
  const geographic = weightType === "GEO";
1244
- if (!geographic && !PLANAR.has(weightType) && weightType !== "EXPLICIT") throw new NativeParseError(FORMAT$2, 1, `expected EDGE_WEIGHT_TYPE EUC_2D, CEIL_2D, ATT, GEO or EXPLICIT, found ${JSON.stringify(weightType)}`);
1296
+ if (!geographic && !PLANAR.has(weightType) && weightType !== "EXPLICIT") throw new NativeParseError(FORMAT$3, 1, `expected EDGE_WEIGHT_TYPE EUC_2D, CEIL_2D, ATT, GEO or EXPLICIT, found ${JSON.stringify(weightType)}`);
1245
1297
  const dimension = wholeOf(required(header, "DIMENSION"), 1, "DIMENSION");
1246
1298
  const budget = numberOf$1(required(header, "TMAX"), 1, "TMAX");
1247
- if (weightType === "EXPLICIT" ? coords.size > 0 : coords.size !== dimension) throw new NativeParseError(FORMAT$2, 1, weightType === "EXPLICIT" ? "an EXPLICIT file has no NODE_COORD_SECTION" : `expected ${String(dimension)} coordinate rows, found ${String(coords.size)}`);
1299
+ if (weightType === "EXPLICIT" ? coords.size > 0 : coords.size !== dimension) throw new NativeParseError(FORMAT$3, 1, weightType === "EXPLICIT" ? "an EXPLICIT file has no NODE_COORD_SECTION" : `expected ${String(dimension)} coordinate rows, found ${String(coords.size)}`);
1248
1300
  const inRange = (id) => id >= 1 && id <= dimension;
1249
1301
  for (const id of [
1250
1302
  ...coords.keys(),
1251
1303
  ...prizes.keys(),
1252
1304
  ...sets.flatMap((set) => set.members)
1253
- ]) if (!inRange(id)) throw new NativeParseError(FORMAT$2, 1, `node ${String(id)} is outside 1 to ${String(dimension)}`);
1254
- if (type === "POP" && prizes.size !== dimension) throw new NativeParseError(FORMAT$2, 1, `expected ${String(dimension)} prize rows, found ${String(prizes.size)}`);
1305
+ ]) if (!inRange(id)) throw new NativeParseError(FORMAT$3, 1, `node ${String(id)} is outside 1 to ${String(dimension)}`);
1306
+ if (type === "POP" && prizes.size !== dimension) throw new NativeParseError(FORMAT$3, 1, `expected ${String(dimension)} prize rows, found ${String(prizes.size)}`);
1255
1307
  if (type === "COP") {
1256
1308
  const declared = wholeOf(required(header, "SETS"), 1, "SETS");
1257
- if (sets.length !== declared) throw new NativeParseError(FORMAT$2, 1, `expected ${String(declared)} sets, found ${String(sets.length)}`);
1309
+ if (sets.length !== declared) throw new NativeParseError(FORMAT$3, 1, `expected ${String(declared)} sets, found ${String(sets.length)}`);
1258
1310
  }
1259
1311
  const clustersOf = (node) => {
1260
1312
  const ids = sets.filter((set) => set.members.includes(node)).map((set) => String(set.id));
@@ -1291,6 +1343,221 @@ function parseTsplib(text) {
1291
1343
  };
1292
1344
  }
1293
1345
  //#endregion
1346
+ //#region src/parsers/ucinet-dl.ts
1347
+ const FORMAT$2 = "ucinet-dl";
1348
+ /** A label section's keyword, upper-cased with its spacing collapsed, mapped to which labels it gives: `LABELS:` names the rows and columns of a one-mode matrix at once, `LEVEL LABELS:` (or `MATRIX LABELS:`) names the matrices. */
1349
+ const LABEL_SECTIONS = /* @__PURE__ */ new Map([
1350
+ ["LABELS", "both"],
1351
+ ["ROW LABELS", "rows"],
1352
+ ["COLUMN LABELS", "columns"],
1353
+ ["COL LABELS", "columns"],
1354
+ ["LEVEL LABELS", "matrices"],
1355
+ ["MATRIX LABELS", "matrices"]
1356
+ ]);
1357
+ /** The FORMAT values that name the full matrix layout, the only one this version reads. */
1358
+ const FULL_MATRIX = /* @__PURE__ */ new Set(["FULLMATRIX", "FM"]);
1359
+ const NUMBER$1 = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?$/;
1360
+ /**
1361
+ * Splits label text into labels: separated by commas, spaces or line breaks, and a double-quoted label may hold spaces or commas.
1362
+ * @param text - The text.
1363
+ * @returns The labels.
1364
+ */
1365
+ function labelsOf(text) {
1366
+ return [...text.matchAll(/"([^"]*)"|[^\s,]+/g)].map((match) => match[1] ?? match[0]);
1367
+ }
1368
+ /**
1369
+ * Reads a dimension parameter's value.
1370
+ * @param value - The text after `=`.
1371
+ * @param key - The parameter, for the error.
1372
+ * @param line - The 1-based line, for the error.
1373
+ * @returns The dimension.
1374
+ */
1375
+ function dimension(value, key, line) {
1376
+ if (!/^[1-9]\d*$/.test(value)) throw new NativeParseError(FORMAT$2, line, `${key} must be a positive integer, found ${JSON.stringify(value)}`);
1377
+ return Number(value);
1378
+ }
1379
+ /** The dimension parameters, upper-cased, mapped to the header field each sets: N actors of a one-mode matrix, NR rows and NC columns of a two-mode one, NM matrices. */
1380
+ const DIMENSIONS = /* @__PURE__ */ new Map([
1381
+ ["N", "n"],
1382
+ ["NR", "nr"],
1383
+ ["NC", "nc"],
1384
+ ["NM", "nm"]
1385
+ ]);
1386
+ /**
1387
+ * Reads the parameters on one header line (`DL N=58 NM=2`, `FORMAT = FULLMATRIX DIAGONAL PRESENT`), separated by spaces or commas, with any spacing around `=`.
1388
+ * @param text - The line, after any leading `DL`.
1389
+ * @param line - The 1-based line, for the error.
1390
+ * @returns The parameters, in line order.
1391
+ */
1392
+ function readParameters(text, line) {
1393
+ const parameters = [];
1394
+ const tokens = text.replace(/\s*=\s*/g, "=").split(/[\s,]+/).filter((token) => token !== "");
1395
+ for (let token = tokens.shift(); token !== void 0; token = tokens.shift()) {
1396
+ const upper = token.toUpperCase();
1397
+ const equals = upper.indexOf("=");
1398
+ if (equals === -1) {
1399
+ const next = tokens[0]?.toUpperCase();
1400
+ if (upper === "DIAGONAL" && (next === "PRESENT" || next === "ABSENT")) {
1401
+ parameters.push({
1402
+ kind: "diagonal",
1403
+ value: next
1404
+ });
1405
+ tokens.shift();
1406
+ continue;
1407
+ }
1408
+ throw new NativeParseError(FORMAT$2, line, `unknown header token ${JSON.stringify(token)}`);
1409
+ }
1410
+ const key = upper.slice(0, equals);
1411
+ const value = token.slice(equals + 1);
1412
+ const field = DIMENSIONS.get(key);
1413
+ if (field !== void 0) parameters.push({
1414
+ kind: "dimension",
1415
+ field,
1416
+ key,
1417
+ value: dimension(value, key, line)
1418
+ });
1419
+ else if (key === "FORMAT") parameters.push({
1420
+ kind: "format",
1421
+ value: value.toUpperCase()
1422
+ });
1423
+ else throw new NativeParseError(FORMAT$2, line, `unknown header parameter ${key}`);
1424
+ }
1425
+ return parameters;
1426
+ }
1427
+ /**
1428
+ * Checks a label list's length and uniqueness.
1429
+ * @param labels - The section, when the file has one.
1430
+ * @param count - How many labels it must hold.
1431
+ * @param what - What the labels name, for the error.
1432
+ * @returns The labels, or undefined when the file has none.
1433
+ */
1434
+ function checked(labels, count, what) {
1435
+ if (labels === void 0) return;
1436
+ if (labels.values.length !== count) throw new NativeParseError(FORMAT$2, labels.line, `expected ${String(count)} ${what} labels, found ${String(labels.values.length)}`);
1437
+ const seen = /* @__PURE__ */ new Set();
1438
+ for (const label of labels.values) {
1439
+ if (seen.has(label)) throw new NativeParseError(FORMAT$2, labels.line, `${what} label ${JSON.stringify(label)} appears twice`);
1440
+ seen.add(label);
1441
+ }
1442
+ return labels.values;
1443
+ }
1444
+ /**
1445
+ * Parses a UCINet DL file (the Data Language of Borgatti, Everett and Freeman's UCINET, as its help describes it) in the full matrix layout as a graph, reading the one matrix the recipe names by its 1-based position among the file's `NM` matrices.
1446
+ *
1447
+ * A one-mode file (`N=n`) has nodes for its n actors, named by their `LABELS:`, `ROW LABELS:` or `COLUMN LABELS:` (which must agree where more than one is given) and otherwise numbered `1` to `n`. A two-mode file (`NR=r NC=c`) has nodes for its r rows followed by its c columns, named by `ROW LABELS:` and `COLUMN LABELS:` and otherwise `r1` to `rR` and `c1` to `cC`; a row and a column may not share a name, because they are different entities. Every non-zero cell of the chosen matrix is one edge from its row to its column, in row-major order, so a symmetric matrix gives each tie in both directions; cell values (tie strengths) are not carried in this version, nor are the `LEVEL LABELS:` that name the matrices, which are only checked to number `NM`.
1448
+ * @param text - The file text.
1449
+ * @param options - The recipe's parser, which names the matrix to read.
1450
+ * @returns The native graph.
1451
+ * @throws NativeParseError naming the line for a file that does not start with `DL`, a missing or contradictory dimension, a layout other than the full matrix, an absent diagonal, embedded labels, a label list of the wrong length or with a repeat, a matrix position beyond `NM`, a non-numeric cell, or a data section that does not hold exactly NM full matrices.
1452
+ */
1453
+ function parseUcinetDl(text, options) {
1454
+ const all = lines(text);
1455
+ const header = { labels: {} };
1456
+ const setParameters = (parameterText, line) => {
1457
+ for (const parameter of readParameters(parameterText, line)) if (parameter.kind === "dimension") {
1458
+ if (header[parameter.field] !== void 0) throw new NativeParseError(FORMAT$2, line, `${parameter.key} is declared twice`);
1459
+ header[parameter.field] = parameter.value;
1460
+ } else if (parameter.kind === "format") header.format = parameter.value;
1461
+ else header.diagonal = parameter.value;
1462
+ };
1463
+ let started = false;
1464
+ let section;
1465
+ let dataLine;
1466
+ const cells = [];
1467
+ for (const [index, raw] of all.entries()) {
1468
+ const lineNumber = index + 1;
1469
+ const content = raw.trim();
1470
+ if (dataLine !== void 0) {
1471
+ for (const cell of content.split(/\s+/).filter((token) => token !== "")) cells.push({
1472
+ text: cell,
1473
+ line: lineNumber
1474
+ });
1475
+ continue;
1476
+ }
1477
+ if (content === "") continue;
1478
+ if (!started) {
1479
+ const match = /^DL(?:\s+(.*))?$/i.exec(content);
1480
+ if (match === null) throw new NativeParseError(FORMAT$2, lineNumber, "a DL file starts with the keyword DL");
1481
+ started = true;
1482
+ if (match[1] !== void 0) setParameters(match[1], lineNumber);
1483
+ continue;
1484
+ }
1485
+ const colon = content.indexOf(":");
1486
+ const keyword = colon === -1 ? void 0 : content.slice(0, colon).trim().toUpperCase().replace(/\s+/g, " ");
1487
+ const rest = content.slice(colon + 1);
1488
+ if (keyword === "DATA") {
1489
+ dataLine = lineNumber;
1490
+ for (const cell of rest.split(/\s+/).filter((token) => token !== "")) cells.push({
1491
+ text: cell,
1492
+ line: lineNumber
1493
+ });
1494
+ continue;
1495
+ }
1496
+ const target = keyword === void 0 ? void 0 : LABEL_SECTIONS.get(keyword);
1497
+ if (target !== void 0) {
1498
+ if (header.labels[target] !== void 0) throw new NativeParseError(FORMAT$2, lineNumber, `${String(keyword)} is given twice`);
1499
+ section = target;
1500
+ header.labels[target] = {
1501
+ values: labelsOf(rest),
1502
+ line: lineNumber
1503
+ };
1504
+ continue;
1505
+ }
1506
+ if (/^LABELS\s+EMBEDDED\b/i.test(content)) throw new NativeParseError(FORMAT$2, lineNumber, "labels embedded in the data are not read in this version");
1507
+ if (section === void 0) setParameters(content, lineNumber);
1508
+ else header.labels[section]?.values.push(...labelsOf(content));
1509
+ }
1510
+ if (!started) throw new NativeParseError(FORMAT$2, 1, "the file is empty");
1511
+ if (dataLine === void 0) throw new NativeParseError(FORMAT$2, all.length, "the file has no DATA: section");
1512
+ const format = header.format ?? "FULLMATRIX";
1513
+ if (!FULL_MATRIX.has(format)) throw new NativeParseError(FORMAT$2, dataLine, `format ${format} is not read in this version; only the full matrix layout is`);
1514
+ if (header.diagonal === "ABSENT") throw new NativeParseError(FORMAT$2, dataLine, "a full matrix with its diagonal absent is not read in this version");
1515
+ const twoMode = header.nr !== void 0 || header.nc !== void 0;
1516
+ if (twoMode === (header.n !== void 0) || twoMode && (header.nr === void 0 || header.nc === void 0)) throw new NativeParseError(FORMAT$2, dataLine, "the header must give either N, or both NR and NC");
1517
+ const rows = header.n ?? header.nr ?? 0;
1518
+ const columns = header.n ?? header.nc ?? 0;
1519
+ const matrices = header.nm ?? 1;
1520
+ checked(header.labels.matrices, matrices, "matrix");
1521
+ if (options.matrix > matrices) throw new NativeParseError(FORMAT$2, dataLine, `the recipe reads matrix ${String(options.matrix)} but the file holds ${String(matrices)}`);
1522
+ let rowIds;
1523
+ let columnIds;
1524
+ if (twoMode) {
1525
+ if (header.labels.both !== void 0) throw new NativeParseError(FORMAT$2, header.labels.both.line, "a two-mode file names its rows and columns with ROW LABELS: and COLUMN LABELS:, not LABELS:");
1526
+ rowIds = checked(header.labels.rows, rows, "row") ?? Array.from({ length: rows }, (_, row) => `r${String(row + 1)}`);
1527
+ columnIds = checked(header.labels.columns, columns, "column") ?? Array.from({ length: columns }, (_, column) => `c${String(column + 1)}`);
1528
+ const rowSet = new Set(rowIds);
1529
+ const shared = columnIds.find((id) => rowSet.has(id));
1530
+ if (shared !== void 0) throw new NativeParseError(FORMAT$2, dataLine, `the row and column label ${JSON.stringify(shared)} names two different entities`);
1531
+ } else {
1532
+ const given = [
1533
+ checked(header.labels.both, rows, "actor"),
1534
+ checked(header.labels.rows, rows, "row"),
1535
+ checked(header.labels.columns, columns, "column")
1536
+ ].filter((labels) => labels !== void 0);
1537
+ const [first] = given;
1538
+ if (first !== void 0 && given.some((labels) => labels.some((label, position) => label !== first[position]))) throw new NativeParseError(FORMAT$2, dataLine, "a one-mode file's row and column labels must be the same actors in the same order");
1539
+ rowIds = first ?? Array.from({ length: rows }, (_, row) => String(row + 1));
1540
+ columnIds = rowIds;
1541
+ }
1542
+ const perMatrix = rows * columns;
1543
+ if (cells.length !== perMatrix * matrices) throw new NativeParseError(FORMAT$2, cells.at(-1)?.line ?? dataLine, `expected ${String(matrices)} full ${String(rows)} by ${String(columns)} matrices (${String(perMatrix * matrices)} cells), found ${String(cells.length)} cells`);
1544
+ for (const cell of cells) if (!NUMBER$1.test(cell.text)) throw new NativeParseError(FORMAT$2, cell.line, `cell ${JSON.stringify(cell.text)} is not a number`);
1545
+ const chosen = cells.slice((options.matrix - 1) * perMatrix, options.matrix * perMatrix);
1546
+ const edges = [];
1547
+ for (const [row, source] of rowIds.entries()) for (const [column, target] of columnIds.entries()) {
1548
+ const cell = chosen[row * columns + column];
1549
+ if (cell === void 0) throw new NativeParseError(FORMAT$2, dataLine, `matrix ${String(options.matrix)} has no cell at row ${String(row + 1)}, column ${String(column + 1)}`);
1550
+ if (Number(cell.text) !== 0) edges.push({
1551
+ source,
1552
+ target
1553
+ });
1554
+ }
1555
+ return {
1556
+ nodes: (twoMode ? [...rowIds, ...columnIds] : rowIds).map((id) => ({ id })),
1557
+ edges
1558
+ };
1559
+ }
1560
+ //#endregion
1294
1561
  //#region src/parsers/verolog-2019.ts
1295
1562
  const FORMAT$1 = "verolog-2019";
1296
1563
  /** The header lines, in the order the file has them, with the key of `meta` each fills and whether its value is text. */
@@ -1889,6 +2156,8 @@ function parseNative(bytes, parser) {
1889
2156
  if (parser.format === "edge-list") return parseEdgeList(text, parser);
1890
2157
  if (parser.format === "matrix-market") return parseMatrixMarket(text);
1891
2158
  if (parser.format === "gml") return parseGml(text);
2159
+ if (parser.format === "ucinet-dl") return parseUcinetDl(text, parser);
2160
+ if (parser.format === "gd-contest") return parseGdContest(text);
1892
2161
  if (parser.format === "customer-points") return parseCustomerPoints(text);
1893
2162
  if (parser.format === "solomon") return parseSolomon(text);
1894
2163
  if (parser.format === "top") return parseTop(text);
@@ -2330,11 +2599,11 @@ function pick(paths, member) {
2330
2599
  return only;
2331
2600
  }
2332
2601
  /**
2333
- * Unpacks downloaded bytes into the one file the parser reads.
2602
+ * Unpacks downloaded bytes into the one file the parser reads. A member with an `inner` archive is unpacked again by that layout, and the path reported is the outer member's, joined by `/` to the inner member's when the inner layout selects one, as though the inner archive were a directory.
2334
2603
  * @param bytes - The downloaded bytes.
2335
2604
  * @param archive - The recipe's archive layout.
2336
2605
  * @returns The file's bytes, and its member path for a multi-file archive.
2337
- * @throws ArchiveError when the bytes are not a well-formed archive of the stated kind or the selector does not match exactly one member.
2606
+ * @throws ArchiveError when the bytes, or a member's bytes that an `inner` layout reads, are not a well-formed archive of the stated kind or a selector does not match exactly one member.
2338
2607
  */
2339
2608
  function unpack(bytes, archive) {
2340
2609
  if (archive.kind === "none") return { bytes };
@@ -2343,6 +2612,26 @@ function unpack(bytes, archive) {
2343
2612
  bytes: sheetToCsv(bytes, archive.sheet),
2344
2613
  member: archive.sheet
2345
2614
  };
2615
+ const outer = selectMember(bytes, archive);
2616
+ if (archive.inner === void 0) return outer;
2617
+ if (archive.inner.kind === "gzip") return {
2618
+ bytes: gunzip(outer.bytes),
2619
+ member: outer.member
2620
+ };
2621
+ const inner = selectMember(outer.bytes, archive.inner);
2622
+ return {
2623
+ bytes: inner.bytes,
2624
+ member: `${outer.member}/${inner.member}`
2625
+ };
2626
+ }
2627
+ /**
2628
+ * Unpacks the one member a file archive's selector matches.
2629
+ * @param bytes - The archive's bytes.
2630
+ * @param archive - A file archive layout and its selector.
2631
+ * @returns The member's bytes and path.
2632
+ * @throws ArchiveError when the bytes are not a well-formed archive of the stated kind or the selector does not match exactly one member.
2633
+ */
2634
+ function selectMember(bytes, archive) {
2346
2635
  if (archive.kind === "zip") {
2347
2636
  const names = [];
2348
2637
  unzip(bytes, (file) => {