@podlite/schema 0.0.40 → 0.0.42

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 (60) hide show
  1. package/CHANGELOG.podlite +17 -0
  2. package/esm/blocks-helpers.d.ts +5 -0
  3. package/esm/blocks-helpers.js +3 -0
  4. package/esm/blocks-helpers.js.map +1 -1
  5. package/esm/exportAny.d.ts +1 -0
  6. package/esm/exportAny.js +1 -1
  7. package/esm/exportAny.js.map +1 -1
  8. package/esm/exportHtml.js +47 -5
  9. package/esm/exportHtml.js.map +1 -1
  10. package/esm/exportMarkdown.js +37 -10
  11. package/esm/exportMarkdown.js.map +1 -1
  12. package/esm/grammar.js +603 -497
  13. package/esm/grammar.js.map +1 -1
  14. package/esm/grammarfc.js +418 -406
  15. package/esm/grammarfc.js.map +1 -1
  16. package/esm/helpers/handlers.d.ts +11 -0
  17. package/esm/helpers/handlers.js +25 -0
  18. package/esm/helpers/handlers.js.map +1 -1
  19. package/esm/helpers/makeInterator.js +2 -4
  20. package/esm/helpers/makeInterator.js.map +1 -1
  21. package/esm/helpers/parseAttributes.d.ts +2 -0
  22. package/esm/helpers/parseAttributes.js +12 -0
  23. package/esm/helpers/parseAttributes.js.map +1 -0
  24. package/esm/index.d.ts +1 -0
  25. package/esm/index.js +3 -0
  26. package/esm/index.js.map +1 -1
  27. package/esm/plugin-data-table.d.ts +2 -0
  28. package/esm/plugin-data-table.js +152 -0
  29. package/esm/plugin-data-table.js.map +1 -0
  30. package/esm/plugin-formatting-codes.js +10 -23
  31. package/esm/plugin-formatting-codes.js.map +1 -1
  32. package/esm/plugin-tables.d.ts +10 -0
  33. package/esm/plugin-tables.js +7 -7
  34. package/esm/plugin-tables.js.map +1 -1
  35. package/esm/selectors.d.ts +17 -1
  36. package/esm/selectors.js +294 -18
  37. package/esm/selectors.js.map +1 -1
  38. package/lib/blocks-helpers.d.ts +5 -0
  39. package/lib/blocks-helpers.js +5 -1
  40. package/lib/exportAny.d.ts +1 -0
  41. package/lib/exportAny.js +1 -1
  42. package/lib/exportHtml.js +46 -4
  43. package/lib/exportMarkdown.js +36 -9
  44. package/lib/grammar.js +603 -497
  45. package/lib/grammarfc.js +418 -406
  46. package/lib/helpers/handlers.d.ts +11 -0
  47. package/lib/helpers/handlers.js +28 -1
  48. package/lib/helpers/makeInterator.js +2 -4
  49. package/lib/helpers/parseAttributes.d.ts +2 -0
  50. package/lib/helpers/parseAttributes.js +35 -0
  51. package/lib/index.d.ts +1 -0
  52. package/lib/index.js +5 -1
  53. package/lib/plugin-data-table.d.ts +2 -0
  54. package/lib/plugin-data-table.js +157 -0
  55. package/lib/plugin-formatting-codes.js +10 -23
  56. package/lib/plugin-tables.d.ts +10 -0
  57. package/lib/plugin-tables.js +8 -0
  58. package/lib/selectors.d.ts +17 -1
  59. package/lib/selectors.js +294 -18
  60. package/package.json +2 -2
@@ -7,6 +7,17 @@ export declare const wrapContent: (pre: any, post: any) => RuleHandler;
7
7
  * emptyContent - skip any child node
8
8
  */
9
9
  export declare const emptyContent: () => RuleHandler;
10
+ /**
11
+ * Replace each non-whitespace character with the mask character.
12
+ * Whitespace (spaces, tabs, newlines) is preserved.
13
+ */
14
+ export declare const maskText: (s: string) => string;
15
+ /**
16
+ * Walk a content array (strings or fcode/text nodes) and return concatenated
17
+ * text content for masking. Nested formatting codes are flattened — masked
18
+ * output drops formatting structure by design.
19
+ */
20
+ export declare const collectText: (content: unknown) => string;
10
21
  /**
11
22
  * content - process childs as regular content
12
23
  */
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.handleNested = exports.subUse = exports.setContext = exports.setFn = exports.content = exports.emptyContent = exports.wrapContent = void 0;
6
+ exports.handleNested = exports.subUse = exports.setContext = exports.setFn = exports.content = exports.collectText = exports.maskText = exports.emptyContent = exports.wrapContent = void 0;
7
7
  const makeQuery_1 = require("./makeQuery");
8
8
  const makeInterator_1 = __importDefault(require("./makeInterator"));
9
9
  const config_1 = __importDefault(require("./config"));
@@ -22,6 +22,33 @@ exports.wrapContent = wrapContent;
22
22
  */
23
23
  const emptyContent = () => () => () => { };
24
24
  exports.emptyContent = emptyContent;
25
+ const MASK_CHAR = '█';
26
+ /**
27
+ * Replace each non-whitespace character with the mask character.
28
+ * Whitespace (spaces, tabs, newlines) is preserved.
29
+ */
30
+ const maskText = (s) => s.replace(/\S/g, MASK_CHAR);
31
+ exports.maskText = maskText;
32
+ /**
33
+ * Walk a content array (strings or fcode/text nodes) and return concatenated
34
+ * text content for masking. Nested formatting codes are flattened — masked
35
+ * output drops formatting structure by design.
36
+ */
37
+ const collectText = (content) => {
38
+ if (typeof content === 'string')
39
+ return content;
40
+ if (!Array.isArray(content)) {
41
+ if (content && typeof content === 'object') {
42
+ const node = content;
43
+ if (node.type === 'text' || node.type === 'verbatim')
44
+ return node.value || '';
45
+ return (0, exports.collectText)(node.content);
46
+ }
47
+ return '';
48
+ }
49
+ return content.map(exports.collectText).join('');
50
+ };
51
+ exports.collectText = collectText;
25
52
  /**
26
53
  * content - process childs as regular content
27
54
  */
@@ -29,13 +29,11 @@ function thisFunc(rules) {
29
29
  const nextRuleIndex = nextRuleSet.findIndex(rule => rule.isFor(node));
30
30
  const defaultFn = (n = node, ctx = context, localInterator = interator) => {
31
31
  if (nextRuleIndex !== -1) {
32
- nextRuleSet[nextRuleIndex].fn(n, ctx, localInterator, () => {
32
+ return nextRuleSet[nextRuleIndex].fn(n, ctx, localInterator, () => {
33
33
  /* empty default action */
34
34
  });
35
35
  }
36
- else {
37
- return;
38
- }
36
+ return;
39
37
  };
40
38
  if (typeof reversed[ruleIndex].fn !== 'function') {
41
39
  console.warn('[podlite] bad fn for ' + JSON.stringify(node, null, 2));
@@ -0,0 +1,2 @@
1
+ import { ConfigItem } from '../types';
2
+ export declare function parseAttributes(src: string): ConfigItem[];
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.parseAttributes = void 0;
23
+ const parser = __importStar(require("../grammar"));
24
+ function parseAttributes(src) {
25
+ if (!src || !src.trim())
26
+ return [];
27
+ try {
28
+ return parser.parse(src, { startRule: 'attributesOnly' });
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ }
34
+ exports.parseAttributes = parseAttributes;
35
+ //# sourceMappingURL=parseAttributes.js.map
package/lib/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export { makeInterator } from './ast-inerator';
11
11
  export { makeTransformer, isNamedBlock, isSemanticBlock } from './helpers/makeTransformer';
12
12
  export { toAny } from './exportAny';
13
13
  export { makeAttrs } from './helpers/config';
14
+ export { parseAttributes } from './helpers/parseAttributes';
14
15
  export { pluginCleanLocation } from './plugin-clean-location';
15
16
  export { toAnyRules } from './helpers/plugins';
16
17
  export { podlitePluggable, Podlite, PodliteExport, cleanIds, frozenIds } from './pluggableParser';
package/lib/index.js CHANGED
@@ -25,7 +25,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
25
25
  return (mod && mod.__esModule) ? mod : { "default": mod };
26
26
  };
27
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
- exports.version = exports.runSelector = exports.parseSelector = exports.Writer = exports.toMarkdown = exports.toHtml = exports.parse = exports.toTree = exports.isValidateError = exports.validateAst = exports.validateAstTree = exports.validatePodliteAst = exports.getPodContentFromNode = exports.getTextContentFromNode = exports.toAst = exports.frozenIds = exports.cleanIds = exports.podlitePluggable = exports.toAnyRules = exports.pluginCleanLocation = exports.makeAttrs = exports.toAny = exports.isSemanticBlock = exports.isNamedBlock = exports.makeTransformer = exports.makeInterator = void 0;
28
+ exports.version = exports.runSelector = exports.parseSelector = exports.Writer = exports.toMarkdown = exports.toHtml = exports.parse = exports.toTree = exports.isValidateError = exports.validateAst = exports.validateAstTree = exports.validatePodliteAst = exports.getPodContentFromNode = exports.getTextContentFromNode = exports.toAst = exports.frozenIds = exports.cleanIds = exports.podlitePluggable = exports.toAnyRules = exports.pluginCleanLocation = exports.parseAttributes = exports.makeAttrs = exports.toAny = exports.isSemanticBlock = exports.isNamedBlock = exports.makeTransformer = exports.makeInterator = void 0;
29
29
  const ajv_1 = __importDefault(require("ajv"));
30
30
  const pointer = __importStar(require("json-pointer"));
31
31
  const jsonShemes = __importStar(require("../schema"));
@@ -45,6 +45,8 @@ var exportAny_1 = require("./exportAny");
45
45
  Object.defineProperty(exports, "toAny", { enumerable: true, get: function () { return exportAny_1.toAny; } });
46
46
  var config_1 = require("./helpers/config");
47
47
  Object.defineProperty(exports, "makeAttrs", { enumerable: true, get: function () { return config_1.makeAttrs; } });
48
+ var parseAttributes_1 = require("./helpers/parseAttributes");
49
+ Object.defineProperty(exports, "parseAttributes", { enumerable: true, get: function () { return parseAttributes_1.parseAttributes; } });
48
50
  var plugin_clean_location_1 = require("./plugin-clean-location");
49
51
  Object.defineProperty(exports, "pluginCleanLocation", { enumerable: true, get: function () { return plugin_clean_location_1.pluginCleanLocation; } });
50
52
  var plugins_1 = require("./helpers/plugins");
@@ -62,6 +64,7 @@ const plugin_group_defn_1 = __importDefault(require("./plugin-group-defn"));
62
64
  const plugin_group_items_1 = __importDefault(require("./plugin-group-items"));
63
65
  const plugin_defn_fill_term_1 = __importDefault(require("./plugin-defn-fill-term"));
64
66
  const plugin_tables_1 = __importDefault(require("./plugin-tables"));
67
+ const plugin_data_table_1 = __importDefault(require("./plugin-data-table"));
65
68
  function toAst() {
66
69
  return {};
67
70
  }
@@ -150,6 +153,7 @@ function makeTree() {
150
153
  chain.use(plugin_heading_1.default);
151
154
  chain.use(plugin_defn_fill_term_1.default);
152
155
  chain.use(plugin_tables_1.default);
156
+ chain.use(plugin_data_table_1.default);
153
157
  chain.use(plugin_formatting_codes_1.default);
154
158
  // save order for the next two plugins
155
159
  chain.use(plugin_group_items_1.default);
@@ -0,0 +1,2 @@
1
+ declare const _default: () => (tree: any) => any;
2
+ export default _default;
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const makeTransformer_1 = __importDefault(require("./helpers/makeTransformer"));
7
+ const config_1 = __importDefault(require("./helpers/config"));
8
+ const plugin_tables_1 = require("./plugin-tables");
9
+ const extensionMimeMap = {
10
+ csv: 'text/csv; header=present',
11
+ tsv: 'text/tab-separated-values; header=present',
12
+ tab: 'text/tab-separated-values; header=present',
13
+ };
14
+ function inferMimeFromExtension(path) {
15
+ const m = path.match(/\.([a-z0-9]+)$/i);
16
+ if (!m)
17
+ return undefined;
18
+ return extensionMimeMap[m[1].toLowerCase()];
19
+ }
20
+ function parseColumnsList(values) {
21
+ if (!Array.isArray(values) || values.length === 0)
22
+ return [];
23
+ if (values.length === 1 && typeof values[0] === 'string' && values[0].includes(',')) {
24
+ return values[0]
25
+ .split(',')
26
+ .map(s => s.trim())
27
+ .filter(s => s.length > 0);
28
+ }
29
+ return values.map(v => String(v).trim()).filter(s => s.length > 0);
30
+ }
31
+ function resolveColumnIndex(col, headerRow, maxCols) {
32
+ const trimmed = col.trim();
33
+ if (/^\d+$/.test(trimmed)) {
34
+ const n = parseInt(trimmed, 10);
35
+ const idx = n - 1;
36
+ if (idx < 0 || idx >= maxCols) {
37
+ throw new Error(`column index ${n} out of range (1..${maxCols})`);
38
+ }
39
+ return idx;
40
+ }
41
+ if (!headerRow) {
42
+ throw new Error(`column name "${col}" requires header=present`);
43
+ }
44
+ const idx = headerRow.indexOf(col);
45
+ if (idx === -1) {
46
+ throw new Error(`column name "${col}" not found in header`);
47
+ }
48
+ return idx;
49
+ }
50
+ function projectColumns(rows, columns, hasHeader) {
51
+ if (rows.length === 0)
52
+ return rows;
53
+ const maxCols = rows[0].length;
54
+ const headerRow = hasHeader ? rows[0] : null;
55
+ const indices = columns.map(col => resolveColumnIndex(col, headerRow, maxCols));
56
+ return rows.map(row => indices.map(i => row[i] ?? ''));
57
+ }
58
+ function applyRename(rows, rename, hasHeader) {
59
+ if (!hasHeader || rows.length === 0)
60
+ return rows;
61
+ const headerRow = [...rows[0]];
62
+ for (const [key, newName] of Object.entries(rename)) {
63
+ const idx = resolveColumnIndex(key, headerRow, headerRow.length);
64
+ headerRow[idx] = String(newName);
65
+ }
66
+ return [headerRow, ...rows.slice(1)];
67
+ }
68
+ function processDataTable(node, tree) {
69
+ const attrs = (0, config_1.default)(node, {});
70
+ const srcRaw = attrs.getFirstValue('src');
71
+ const mimeAttr = attrs.getFirstValue('mime-type');
72
+ const renameMap = attrs.getMapValue('rename');
73
+ const columnsPresent = attrs.exists('columns');
74
+ const columns = columnsPresent ? parseColumnsList(attrs.getAllValues('columns')) : null;
75
+ const bodyText = (0, plugin_tables_1.extractDataText)(node);
76
+ const hasInlineBody = bodyText.trim().length > 0;
77
+ if (srcRaw && hasInlineBody) {
78
+ throw new Error(':src and inline body are mutually exclusive');
79
+ }
80
+ if (columnsPresent && (!columns || columns.length === 0)) {
81
+ throw new Error(':columns must have at least one entry; omit the attribute to render all columns');
82
+ }
83
+ let csvText;
84
+ let mimeType;
85
+ let hasHeader;
86
+ if (srcRaw) {
87
+ const srcStr = String(srcRaw);
88
+ const m = srcStr.match(/^(file|data|http|https|doc):(.+)$/);
89
+ const scheme = m ? m[1] : 'file';
90
+ const target = m ? m[2] : srcStr;
91
+ if (scheme === 'data') {
92
+ const dataBlock = (0, plugin_tables_1.findDataBlockByKey)(tree, target);
93
+ if (!dataBlock) {
94
+ console.warn(`[data-table] no =data block found for data:${target} — rendered as empty`);
95
+ return { ...node, name: 'table', content: [] };
96
+ }
97
+ if (mimeAttr) {
98
+ throw new Error(`:mime-type cannot be declared with :src<data:${target}> (inherited from referenced block)`);
99
+ }
100
+ const dataMime = (0, config_1.default)(dataBlock, {}).getFirstValue('mime-type');
101
+ if (!dataMime) {
102
+ console.warn(`[data-table] =data block ${target} has no :mime-type — rendered as empty`);
103
+ return { ...node, name: 'table', content: [] };
104
+ }
105
+ const parsed = (0, plugin_tables_1.parseMimeType)(dataMime);
106
+ mimeType = parsed.type;
107
+ hasHeader = parsed.params.header === 'present';
108
+ csvText = (0, plugin_tables_1.extractDataText)(dataBlock);
109
+ }
110
+ else {
111
+ return node;
112
+ }
113
+ }
114
+ else {
115
+ if (!mimeAttr) {
116
+ throw new Error('inline body requires :mime-type');
117
+ }
118
+ const parsed = (0, plugin_tables_1.parseMimeType)(mimeAttr);
119
+ mimeType = parsed.type;
120
+ hasHeader = parsed.params.header === 'present';
121
+ csvText = bodyText;
122
+ }
123
+ const isCsv = mimeType === 'text/csv';
124
+ const isTsv = mimeType === 'text/tab-separated-values';
125
+ if (!isCsv && !isTsv) {
126
+ console.warn(`[data-table] unsupported mime-type ${mimeType} — rendered as empty`);
127
+ return { ...node, name: 'table', content: [] };
128
+ }
129
+ let rows = isCsv ? (0, plugin_tables_1.parseCsv)(csvText) : (0, plugin_tables_1.parseTsv)(csvText);
130
+ if (rows.length === 0) {
131
+ console.warn(`[data-table] ${isCsv ? 'CSV' : 'TSV'} parse produced no rows — rendered as empty`);
132
+ return { ...node, name: 'table', content: [] };
133
+ }
134
+ if (columns) {
135
+ rows = projectColumns(rows, columns, hasHeader);
136
+ }
137
+ if (renameMap) {
138
+ rows = applyRename(rows, renameMap, hasHeader);
139
+ }
140
+ const filledNode = { ...node, name: 'table', content: (0, plugin_tables_1.csvToTableContent)(rows, hasHeader) };
141
+ return (0, plugin_tables_1.normalizeCellCounts)(filledNode, 'data-table');
142
+ }
143
+ exports.default = () => tree => {
144
+ const transformer = (0, makeTransformer_1.default)({
145
+ 'data-table': node => {
146
+ try {
147
+ return processDataTable(node, tree);
148
+ }
149
+ catch (err) {
150
+ console.warn(`[data-table] ${err.message}`);
151
+ return { ...node, name: 'table', content: [] };
152
+ }
153
+ },
154
+ });
155
+ return transformer(tree);
156
+ };
157
+ //# sourceMappingURL=plugin-data-table.js.map
@@ -47,33 +47,20 @@ const middle = () => tree => {
47
47
  content: visiter(n.content, ctx, visiter),
48
48
  };
49
49
  const conf = (0, config_1.default)(n, ctx);
50
- const isCodeBlock = 'name' in n && n.name === 'code';
51
- const isDataBlock = 'name' in n && n.name === 'data';
52
- const isMarkdownBlock = 'name' in n && n.name === 'markdown';
53
- const isPictureBlock = 'name' in n && n.name === 'picture';
54
- const isFormulaBlock = 'name' in n && n.name === 'formula';
55
- const allowValues = [...conf.getAllValues('allow'), ...(isCodeBlock ? ['NONE'] : [])];
56
- if ((0, makeTransformer_2.isNamedBlock)(n.name))
50
+ const name = 'name' in n ? n.name : '';
51
+ // Blocks whose content is verbatim by default fcode parsing only
52
+ // kicks in when :allow opts in (per spec, "Formatting within code blocks").
53
+ const isVerbatimDefault = ['code', 'data', 'markdown', 'picture', 'formula'].includes(name);
54
+ const allowValues = conf.getAllValues('allow');
55
+ if ((0, makeTransformer_2.isNamedBlock)(name))
57
56
  return n;
58
- // for code block not parse content by default
59
- if ((isCodeBlock || isDataBlock || isMarkdownBlock || isPictureBlock || isFormulaBlock) &&
60
- allowValues.length == 0)
57
+ if (isVerbatimDefault && allowValues.length === 0)
61
58
  return n;
62
59
  const allowed = allowValues.sort();
63
60
  const transformer = (0, makeTransformer_1.default)({
64
- ':verbatim': (n, ctx) => {
65
- // special case for code block, 'NONE' - flag disabled all markup codes
66
- // do not parse content by default
67
- if (allowed.length == 1 && allowed.includes('NONE'))
68
- return n;
69
- return fcparser.parse(n.value, { allowed });
70
- },
71
- ':text': (n, ctx) => {
72
- return fcparser.parse(n.value, { allowed });
73
- },
74
- ':block': (n, ctx) => {
75
- return transformerBlocks(n, { ...ctx });
76
- },
61
+ ':verbatim': (n, ctx) => fcparser.parse(n.value, { allowed }),
62
+ ':text': (n, ctx) => fcparser.parse(n.value, { allowed }),
63
+ ':block': (n, ctx) => transformerBlocks(n, { ...ctx }),
77
64
  });
78
65
  return { ...n, content: transformer(n.content, { ...ctx }) };
79
66
  },
@@ -1,2 +1,12 @@
1
+ export declare function parseCsv(text: any): any[];
2
+ export declare function parseTsv(text: any): any;
3
+ export declare function parseMimeType(raw: any): {
4
+ type: string;
5
+ params: Record<string, string>;
6
+ };
7
+ export declare function findDataBlockByKey(tree: any, key: any): any;
8
+ export declare function extractDataText(dataNode: any): any;
9
+ export declare function csvToTableContent(csvRows: any, hasHeader?: boolean): any;
10
+ export declare function normalizeCellCounts(tableNode: any, source?: string): any;
1
11
  declare const _default: () => (tree: any) => any;
2
12
  export default _default;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.normalizeCellCounts = exports.csvToTableContent = exports.extractDataText = exports.findDataBlockByKey = exports.parseMimeType = exports.parseTsv = exports.parseCsv = void 0;
6
7
  /**
7
8
  * Plugin for fill term's for defn. From S26:
8
9
  * The first non-blank line of content is treated as a term being defined,
@@ -73,6 +74,7 @@ function parseCsv(text) {
73
74
  // Drop rows that are entirely blank (empty content after trim).
74
75
  return rows.filter(r => !(r.length === 1 && r[0].trim() === ''));
75
76
  }
77
+ exports.parseCsv = parseCsv;
76
78
  // Parse a TSV (tab-separated values) blob. Unlike CSV, TSV has no quoting
77
79
  // mechanism — fields are split strictly on tabs and `"` is a literal
78
80
  // character. Tabs and newlines inside fields are not representable in TSV.
@@ -81,6 +83,7 @@ function parseTsv(text) {
81
83
  const rows = lines.map(line => line.split('\t'));
82
84
  return rows.filter(r => !(r.length === 1 && r[0].trim() === ''));
83
85
  }
86
+ exports.parseTsv = parseTsv;
84
87
  // Parse a MIME type value into the bare type and a map of parameters
85
88
  // (RFC 6838 / RFC 7231 §3.1.1.1). Used to recognise the `header` parameter
86
89
  // (RFC 4180 §3) on `:mime-type` of CSV/TSV =data blocks.
@@ -111,6 +114,7 @@ function parseMimeType(raw) {
111
114
  }
112
115
  return { type, params };
113
116
  }
117
+ exports.parseMimeType = parseMimeType;
114
118
  // Locate the first `=data` block with a matching `:key` attribute anywhere
115
119
  // in the document tree.
116
120
  function findDataBlockByKey(tree, key) {
@@ -137,12 +141,14 @@ function findDataBlockByKey(tree, key) {
137
141
  walk(tree);
138
142
  return found;
139
143
  }
144
+ exports.findDataBlockByKey = findDataBlockByKey;
140
145
  function extractDataText(dataNode) {
141
146
  if (!dataNode || !Array.isArray(dataNode.content))
142
147
  return '';
143
148
  const verbatim = dataNode.content.find(c => c && c.type === 'verbatim');
144
149
  return verbatim && typeof verbatim.value === 'string' ? verbatim.value : '';
145
150
  }
151
+ exports.extractDataText = extractDataText;
146
152
  // Detect whether the =table body is a single-line source reference like
147
153
  // `data:key` or `file:path` (spec §1672). Returns the parsed reference or
148
154
  // null if the body is ordinary table content.
@@ -203,6 +209,7 @@ function csvToTableContent(csvRows, hasHeader = false) {
203
209
  return buildRowBlock(cells, hasHeader && i === 0);
204
210
  });
205
211
  }
212
+ exports.csvToTableContent = csvToTableContent;
206
213
  // ─── Error recovery (design notes Rules 2-4) ───────────────────────────────
207
214
  // Rule 2 — table-level cell count validation. Pad short rows with empty
208
215
  // cells; truncate long rows. Emit a warning whenever a row is changed.
@@ -265,6 +272,7 @@ function normalizeCellCounts(tableNode, source = 'table') {
265
272
  });
266
273
  return mutated ? { ...tableNode, content: newContent } : tableNode;
267
274
  }
275
+ exports.normalizeCellCounts = normalizeCellCounts;
268
276
  // Rule 3 — mixed separator detection (text-mode only). Inspects each line
269
277
  // for visible separators (`|` / `+`) surrounded by whitespace; lines without
270
278
  // any visible separator fall back to whitespace separation. Warns when more
@@ -8,11 +8,27 @@ export declare type SelectorDoc = {
8
8
  file: string;
9
9
  node: PodNode | PodliteDocument;
10
10
  };
11
+ export declare type ValueSpec = {
12
+ kind: 'angle';
13
+ value: string;
14
+ } | {
15
+ kind: 'contains';
16
+ value: string;
17
+ };
18
+ export declare type Condition = {
19
+ modifier?: '!' | '?' | '!?';
20
+ attrName: string;
21
+ valueSpec?: ValueSpec;
22
+ };
23
+ export declare type Pattern = {
24
+ blockType: string;
25
+ predicate?: Condition[];
26
+ };
11
27
  export declare type ParsedSelector = {
12
28
  scheme?: string;
13
29
  document?: string;
14
30
  anchor?: string;
15
- blockFilters: string[];
31
+ patterns: Pattern[];
16
32
  };
17
33
  export declare const parseSelector: (selector: string) => ParsedSelector | undefined;
18
34
  export declare const runSelector: <T extends SelectorDoc>(selector: string, docs: T[]) => PodNode[] | T[];