@podlite/markdown 0.0.31 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.esm.js CHANGED
@@ -25644,7 +25644,7 @@ var require_package = __commonJS({
25644
25644
  "../podlite-schema/package.json"(exports, module) {
25645
25645
  module.exports = {
25646
25646
  name: "@podlite/schema",
25647
- version: "0.0.38",
25647
+ version: "0.0.39",
25648
25648
  description: "AST tools and schema for Podlite markup language \u2014 validate, traverse, and transform document trees",
25649
25649
  main: "./src/index.ts",
25650
25650
  homepage: "https://podlite.org",
@@ -27026,6 +27026,223 @@ var plugin_defn_fill_term_default = () => (tree) => {
27026
27026
  function flattenDeep3(arr) {
27027
27027
  return arr.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep3(val)) : acc.concat(val), []);
27028
27028
  }
27029
+ function parseCsv(text4) {
27030
+ const rows = [];
27031
+ let field = "";
27032
+ let row = [];
27033
+ let inQuote = false;
27034
+ let i = 0;
27035
+ while (i < text4.length) {
27036
+ const c = text4[i];
27037
+ if (inQuote) {
27038
+ if (c === '"' && text4[i + 1] === '"') {
27039
+ field += '"';
27040
+ i += 2;
27041
+ continue;
27042
+ }
27043
+ if (c === '"') {
27044
+ inQuote = false;
27045
+ i++;
27046
+ continue;
27047
+ }
27048
+ field += c;
27049
+ i++;
27050
+ continue;
27051
+ }
27052
+ if (c === '"' && field === "") {
27053
+ inQuote = true;
27054
+ i++;
27055
+ continue;
27056
+ }
27057
+ if (c === ",") {
27058
+ row.push(field);
27059
+ field = "";
27060
+ i++;
27061
+ continue;
27062
+ }
27063
+ if (c === "\r") {
27064
+ i++;
27065
+ continue;
27066
+ }
27067
+ if (c === "\n") {
27068
+ row.push(field);
27069
+ rows.push(row);
27070
+ row = [];
27071
+ field = "";
27072
+ i++;
27073
+ continue;
27074
+ }
27075
+ field += c;
27076
+ i++;
27077
+ }
27078
+ if (field !== "" || row.length > 0) {
27079
+ row.push(field);
27080
+ rows.push(row);
27081
+ }
27082
+ return rows.filter((r) => !(r.length === 1 && r[0].trim() === ""));
27083
+ }
27084
+ function parseTsv(text4) {
27085
+ const lines = text4.split(/\r?\n/);
27086
+ const rows = lines.map((line) => line.split(" "));
27087
+ return rows.filter((r) => !(r.length === 1 && r[0].trim() === ""));
27088
+ }
27089
+ function findDataBlockByKey(tree, key) {
27090
+ let found = null;
27091
+ const walk = (node) => {
27092
+ if (found)
27093
+ return;
27094
+ if (Array.isArray(node)) {
27095
+ node.forEach(walk);
27096
+ return;
27097
+ }
27098
+ if (!node || typeof node !== "object")
27099
+ return;
27100
+ if (node.type === "block" && node.name === "data") {
27101
+ const attrs = config_default(node, {});
27102
+ if (attrs.getFirstValue("key") === key) {
27103
+ found = node;
27104
+ return;
27105
+ }
27106
+ }
27107
+ if (Array.isArray(node.content))
27108
+ node.content.forEach(walk);
27109
+ };
27110
+ walk(tree);
27111
+ return found;
27112
+ }
27113
+ function extractDataText(dataNode) {
27114
+ if (!dataNode || !Array.isArray(dataNode.content))
27115
+ return "";
27116
+ const verbatim = dataNode.content.find((c) => c && c.type === "verbatim");
27117
+ return verbatim && typeof verbatim.value === "string" ? verbatim.value : "";
27118
+ }
27119
+ function detectSourceReference(tableNode) {
27120
+ const texts = [];
27121
+ const walker = makeTransformer_default({
27122
+ "row:text": (r) => {
27123
+ texts.push(r.value);
27124
+ return r;
27125
+ },
27126
+ "head:text": (h) => {
27127
+ texts.push(h.value);
27128
+ return h;
27129
+ }
27130
+ });
27131
+ walker(tableNode, {});
27132
+ const joined = texts.join("\n").trim();
27133
+ const lines = joined.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
27134
+ if (lines.length !== 1)
27135
+ return null;
27136
+ const m = lines[0].match(/^(data|file):(\S+)$/);
27137
+ if (!m)
27138
+ return null;
27139
+ return { scheme: m[1], target: m[2] };
27140
+ }
27141
+ function buildCellBlock(text4) {
27142
+ return {
27143
+ name: "cell",
27144
+ type: "block",
27145
+ margin: "",
27146
+ content: [{ type: "text", value: text4 }]
27147
+ };
27148
+ }
27149
+ function buildRowBlock(cells, isHeader) {
27150
+ const block = {
27151
+ name: "row",
27152
+ type: "block",
27153
+ margin: "",
27154
+ content: cells
27155
+ };
27156
+ if (isHeader) {
27157
+ block.config = [{ name: "header", value: true, type: "boolean" }];
27158
+ }
27159
+ return block;
27160
+ }
27161
+ function csvToTableContent(csvRows) {
27162
+ return csvRows.map((row) => {
27163
+ const cells = row.map((v) => buildCellBlock(v.trim()));
27164
+ return buildRowBlock(cells, false);
27165
+ });
27166
+ }
27167
+ function normalizeCellCounts(tableNode, source = "table") {
27168
+ if (!tableNode || !Array.isArray(tableNode.content))
27169
+ return tableNode;
27170
+ const rows = tableNode.content.filter((c) => c && c.type === "block" && c.name === "row");
27171
+ if (rows.length === 0)
27172
+ return tableNode;
27173
+ const cellsOf = (row) => Array.isArray(row.content) ? row.content.filter((c) => c && c.name === "cell") : [];
27174
+ const isHeaderRow = (row) => Array.isArray(row.config) && row.config.some((a) => a.name === "header" && a.value === true);
27175
+ const cellHasSpan = (cell) => Array.isArray(cell.config) && cell.config.some((a) => a.name === "colspan" || a.name === "rowspan");
27176
+ const rowHasSpan = (row) => cellsOf(row).some(cellHasSpan);
27177
+ if (rows.some(rowHasSpan))
27178
+ return tableNode;
27179
+ const headerRow = rows.find(isHeaderRow);
27180
+ const expected = headerRow ? cellsOf(headerRow).length : Math.max(...rows.map((r) => cellsOf(r).length));
27181
+ if (expected === 0)
27182
+ return tableNode;
27183
+ let mutated = false;
27184
+ const newContent = tableNode.content.map((child) => {
27185
+ if (!child || child.type !== "block" || child.name !== "row")
27186
+ return child;
27187
+ const cells = cellsOf(child);
27188
+ if (cells.length === expected)
27189
+ return child;
27190
+ if (cells.length < expected) {
27191
+ const padding = [];
27192
+ for (let i = cells.length; i < expected; i++)
27193
+ padding.push(buildCellBlock(""));
27194
+ console.warn(
27195
+ `[${source}] row has ${cells.length} cells, expected ${expected} \u2014 padded with ${padding.length} empty`
27196
+ );
27197
+ mutated = true;
27198
+ return { ...child, content: [...child.content, ...padding] };
27199
+ }
27200
+ const dropped = cells.length - expected;
27201
+ console.warn(`[${source}] row has ${cells.length} cells, expected ${expected} \u2014 truncated ${dropped}`);
27202
+ mutated = true;
27203
+ let keepCells = expected;
27204
+ const trimmed = [];
27205
+ for (const c of child.content || []) {
27206
+ if (c && c.type === "block" && c.name === "cell") {
27207
+ if (keepCells > 0) {
27208
+ trimmed.push(c);
27209
+ keepCells--;
27210
+ }
27211
+ } else {
27212
+ trimmed.push(c);
27213
+ }
27214
+ }
27215
+ return { ...child, content: trimmed };
27216
+ });
27217
+ return mutated ? { ...tableNode, content: newContent } : tableNode;
27218
+ }
27219
+ function detectMixedSeparators(lines) {
27220
+ const seen = /* @__PURE__ */ new Set();
27221
+ for (const line of lines) {
27222
+ if (!line || typeof line !== "string")
27223
+ continue;
27224
+ if (/\s\|\s/.test(line))
27225
+ seen.add("pipe");
27226
+ else if (/\s\+\s/.test(line))
27227
+ seen.add("plus");
27228
+ else if (line.trim().length > 0)
27229
+ seen.add("whitespace");
27230
+ if (seen.size > 1)
27231
+ break;
27232
+ }
27233
+ if (seen.size > 1) {
27234
+ console.warn(`[table] mixed separator types detected: ${Array.from(seen).join(", ")} \u2014 recommend a single style`);
27235
+ }
27236
+ }
27237
+ function buildCodeFromDataBlock(tableNode, dataBlock) {
27238
+ return {
27239
+ type: "block",
27240
+ name: "code",
27241
+ margin: tableNode.margin || "",
27242
+ content: Array.isArray(dataBlock.content) ? dataBlock.content : [],
27243
+ config: Array.isArray(tableNode.config) ? tableNode.config : []
27244
+ };
27245
+ }
27029
27246
  var strbin = (str1, str2, cb) => {
27030
27247
  let res = [];
27031
27248
  for (let i = 0; str1.length > i; i++) {
@@ -27036,49 +27253,91 @@ var strbin = (str1, str2, cb) => {
27036
27253
  var makeMask = (lines, separators) => {
27037
27254
  const tmplLength = Math.max(...[...lines, ...separators].map((s) => s.length));
27038
27255
  const masks = lines.map((str) => {
27039
- let tstr = str + " ".repeat(tmplLength - str.length);
27040
- let mask = [];
27256
+ const tstr = str + " ".repeat(tmplLength - str.length);
27257
+ const mask = [];
27041
27258
  const re = /\s+[+|\s]\s/g;
27042
27259
  let match;
27043
27260
  while ((match = re.exec(tstr)) != null) {
27044
27261
  const tmpMask = "1".repeat(match.index) + "0".repeat(match[0].length);
27045
27262
  mask.push(tmpMask + "1".repeat(tmplLength - tmpMask.length));
27046
27263
  }
27047
- return mask.reduce((a, b) => {
27048
- return strbin(a, b, (i1, i2) => i1 & i2);
27049
- }, "1".repeat(tmplLength));
27264
+ return mask.reduce((a, b) => strbin(a, b, (i1, i2) => i1 & i2), "1".repeat(tmplLength));
27050
27265
  });
27051
27266
  const inverted = masks.map((m) => strbin(m, "", (i1) => i1 == 0 ? 1 : 0));
27052
- const columnTemplate = inverted.reduce((a, b) => {
27053
- return strbin(a, b, (i1, i2) => i1 & i2);
27054
- }, "1".repeat(tmplLength));
27055
- return columnTemplate;
27267
+ return inverted.reduce((a, b) => strbin(a, b, (i1, i2) => i1 & i2), "1".repeat(tmplLength));
27056
27268
  };
27057
27269
  var extractColumnsByTemplate = (text4, template) => {
27058
- const lines = flattenDeep3(
27059
- text4.split(/\n/).filter((str) => str.length > 0)
27060
- );
27270
+ const lines = flattenDeep3(text4.split(/\n/).filter((s) => s.length > 0));
27061
27271
  const cols = lines.map((line) => {
27062
27272
  const re = /((1+|0+))/g;
27063
- let columns = [];
27273
+ const columns = [];
27064
27274
  let match;
27065
27275
  while ((match = re.exec(template)) != null) {
27066
- if (match[0][0] == 1)
27276
+ if (match[0][0] == "1")
27067
27277
  continue;
27068
- const s = line.substring(match.index, match.index + match[0].length);
27069
- columns.push(s);
27278
+ columns.push(line.substring(match.index, match.index + match[0].length));
27070
27279
  }
27071
27280
  return columns;
27072
27281
  });
27073
- let result = [];
27074
- result = cols.reduce((a, b) => {
27282
+ const result = [];
27283
+ cols.reduce((a, b) => {
27075
27284
  for (let i = 0; i < b.length; i++) {
27076
27285
  a[i] = (a[i] === void 0 ? "" : a[i]) + " " + b[i];
27077
27286
  }
27078
27287
  return a;
27079
- }, []);
27288
+ }, result);
27080
27289
  return result;
27081
27290
  };
27291
+ var detectLineSeparator = (line) => {
27292
+ if (/(?:^|\s)\|(?:\s|$)/.test(line))
27293
+ return "pipe";
27294
+ if (/(?:^|\s)\+(?:\s|$)/.test(line))
27295
+ return "plus";
27296
+ return "whitespace";
27297
+ };
27298
+ var trimEdgeEmpty = (cells) => {
27299
+ let start = 0;
27300
+ let end = cells.length;
27301
+ if (cells[start] === "")
27302
+ start++;
27303
+ if (end > start && cells[end - 1] === "")
27304
+ end--;
27305
+ return cells.slice(start, end);
27306
+ };
27307
+ var splitLineByPipe = (line) => trimEdgeEmpty(line.split(/\s*\|\s*/).map((c) => c.trim()));
27308
+ var splitLineByPlus = (line) => trimEdgeEmpty(line.split(/\s*\+\s*/).map((c) => c.trim()));
27309
+ var splitLineByWhitespace = (line) => line.trim().split(/\s{2,}/).filter((c) => c !== "");
27310
+ var splitLineCells = (line) => {
27311
+ const trimmed = line.trim();
27312
+ if (trimmed === "")
27313
+ return [];
27314
+ const kind = detectLineSeparator(line);
27315
+ if (kind === "pipe")
27316
+ return splitLineByPipe(line);
27317
+ if (kind === "plus")
27318
+ return splitLineByPlus(line);
27319
+ return splitLineByWhitespace(line);
27320
+ };
27321
+ var rowToCells = (rowValue) => {
27322
+ const lines = rowValue.split(/\r?\n/).filter((l) => l.trim() !== "");
27323
+ if (lines.length === 0)
27324
+ return [];
27325
+ if (lines.length === 1)
27326
+ return splitLineCells(lines[0]);
27327
+ const kind = detectLineSeparator(lines[0]);
27328
+ const splitFn = kind === "pipe" ? splitLineByPipe : kind === "plus" ? splitLineByPlus : splitLineByWhitespace;
27329
+ const lineCells = lines.map(splitFn);
27330
+ const maxCols = Math.max(...lineCells.map((c) => c.length));
27331
+ const merged = [];
27332
+ for (let i = 0; i < maxCols; i++) {
27333
+ const parts = lineCells.map((line) => {
27334
+ var _a;
27335
+ return (_a = line[i]) != null ? _a : "";
27336
+ }).filter((p) => p !== "");
27337
+ merged.push(parts.join(" "));
27338
+ }
27339
+ return merged;
27340
+ };
27082
27341
  var wrapImplicitCells = (rowNode) => {
27083
27342
  if (!Array.isArray(rowNode.content) || rowNode.content.length === 0)
27084
27343
  return rowNode;
@@ -27098,13 +27357,40 @@ var isStructured = (tableNode) => Array.isArray(tableNode.content) && tableNode.
27098
27357
  var plugin_tables_default = () => (tree) => {
27099
27358
  const transformer = makeTransformer_default({
27100
27359
  table: (node) => {
27360
+ const ref = detectSourceReference(node);
27361
+ if (ref && ref.scheme === "data") {
27362
+ const dataBlock = findDataBlockByKey(tree, ref.target);
27363
+ if (!dataBlock) {
27364
+ console.warn(`[table] no =data block found for data:${ref.target} \u2014 rendered as empty`);
27365
+ return { ...node, content: [] };
27366
+ }
27367
+ const mimeType = config_default(dataBlock, {}).getFirstValue("mime-type");
27368
+ const isCsv = mimeType === "text/csv";
27369
+ const isTsv = mimeType === "text/tab-separated-values";
27370
+ if (isCsv || isTsv) {
27371
+ const text4 = extractDataText(dataBlock);
27372
+ const rows2 = isCsv ? parseCsv(text4) : parseTsv(text4);
27373
+ if (rows2.length === 0) {
27374
+ console.warn(
27375
+ `[table] ${isCsv ? "CSV" : "TSV"} parse produced no rows for data:${ref.target} \u2014 rendered as empty`
27376
+ );
27377
+ return { ...node, content: [] };
27378
+ }
27379
+ const filledNode = { ...node, content: csvToTableContent(rows2) };
27380
+ return normalizeCellCounts(filledNode, `table data:${ref.target}`);
27381
+ }
27382
+ console.warn(
27383
+ `[table] =data :key<${ref.target}> has non-tabular mime-type ${mimeType || "(none)"} \u2014 rendered as =code`
27384
+ );
27385
+ return buildCodeFromDataBlock(node, dataBlock);
27386
+ }
27101
27387
  if (isStructured(node)) {
27102
27388
  const transformedContent = (node.content || []).map((c) => {
27103
27389
  if (c && c.name === "row")
27104
27390
  return wrapImplicitCells(c);
27105
27391
  return c;
27106
27392
  });
27107
- return { ...node, content: transformedContent };
27393
+ return normalizeCellCounts({ ...node, content: transformedContent }, "table");
27108
27394
  }
27109
27395
  let rows = [];
27110
27396
  const collectValues = (row) => {
@@ -27123,37 +27409,46 @@ var plugin_tables_default = () => (tree) => {
27123
27409
  const splitToLines = (row) => row.split(/\n/).filter((str) => str.length > 0);
27124
27410
  const lines = flattenDeep3(rows.map(splitToLines));
27125
27411
  const separators = flattenDeep3(seps.map(splitToLines));
27412
+ detectMixedSeparators(lines);
27126
27413
  let textRows = [];
27127
27414
  makeTransformer_default({
27128
27415
  "row:text": (row) => {
27129
27416
  textRows.push(row.value);
27130
27417
  }
27131
27418
  })(node);
27132
- const columnTemplate = makeMask(lines, separators);
27133
27419
  const makeBlock = (name, content4, extra = {}) => {
27134
27420
  return { ...extra, name, type: "block", content: Array.isArray(content4) ? content4 : [content4] };
27135
27421
  };
27136
27422
  const makeRow = (cells) => makeBlock("row", cells);
27137
27423
  const makeHeaderRow = (cells) => makeBlock("row", cells, { config: [{ name: "header", value: true, type: "boolean" }] });
27138
27424
  const makeCell = (text4) => makeBlock("cell", { type: "text", value: text4 });
27425
+ const columnTemplate = makeMask(lines, separators);
27426
+ const seenSeparatorKinds = new Set(lines.map(detectLineSeparator));
27427
+ const hasVisible = seenSeparatorKinds.has("pipe") || seenSeparatorKinds.has("plus");
27428
+ const hasWhitespace = seenSeparatorKinds.has("whitespace");
27429
+ const useMixedSplitting = hasVisible && hasWhitespace;
27430
+ const splitToCells = (rowValue) => {
27431
+ if (useMixedSplitting) {
27432
+ const rowLines = rowValue.split(/\r?\n/).filter((l) => l.trim() !== "");
27433
+ if (rowLines.length <= 1)
27434
+ return rowToCells(rowValue);
27435
+ }
27436
+ return extractColumnsByTemplate(rowValue, columnTemplate);
27437
+ };
27139
27438
  const res = makeTransformer_default({
27140
27439
  "row:text": (row) => {
27141
27440
  if (textRows.length == 1) {
27142
27441
  const textRowsLines = flattenDeep3([row.value].map(splitToLines));
27143
- return textRowsLines.map((rowValue) => {
27144
- const cols2 = extractColumnsByTemplate(rowValue, columnTemplate);
27145
- return makeRow(cols2.map(makeCell));
27146
- });
27442
+ if (useMixedSplitting) {
27443
+ return textRowsLines.map((line) => makeRow(splitLineCells(line).map(makeCell)));
27444
+ }
27445
+ return textRowsLines.map((line) => makeRow(extractColumnsByTemplate(line, columnTemplate).map(makeCell)));
27147
27446
  }
27148
- const cols = extractColumnsByTemplate(row.value, columnTemplate);
27149
- return makeRow(cols.map(makeCell));
27447
+ return makeRow(splitToCells(row.value).map(makeCell));
27150
27448
  },
27151
- "head:text": (head) => {
27152
- const cols = extractColumnsByTemplate(head.value, columnTemplate);
27153
- return makeHeaderRow(cols.map(makeCell));
27154
- }
27449
+ "head:text": (head) => makeHeaderRow(splitToCells(head.value).map(makeCell))
27155
27450
  })(node);
27156
- return res;
27451
+ return normalizeCellCounts(res, "table");
27157
27452
  }
27158
27453
  });
27159
27454
  return transformer(tree, {});