@podlite/schema 0.0.65 → 0.0.67
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/CHANGELOG.podlite +28 -0
- package/esm/block-names.d.ts +1 -0
- package/esm/block-names.js +6 -1
- package/esm/block-names.js.map +1 -1
- package/esm/blocks-helpers.d.ts +1 -1
- package/esm/blocks-helpers.js +2 -1
- package/esm/blocks-helpers.js.map +1 -1
- package/esm/exportHtml.js +10 -5
- package/esm/exportHtml.js.map +1 -1
- package/esm/exportMarkdown.js +58 -26
- package/esm/exportMarkdown.js.map +1 -1
- package/esm/grammarfc.js +7794 -6355
- package/esm/grammarfc.js.map +1 -1
- package/esm/guard.d.ts +5 -0
- package/esm/guard.js +39 -0
- package/esm/guard.js.map +1 -0
- package/esm/index.d.ts +2 -1
- package/esm/index.js +2 -1
- package/esm/index.js.map +1 -1
- package/esm/pluggableParser.js +3 -0
- package/esm/pluggableParser.js.map +1 -1
- package/esm/plugin-data-table.d.ts +1 -1
- package/esm/plugin-data-table.js +13 -11
- package/esm/plugin-data-table.js.map +1 -1
- package/esm/plugin-formatting-codes.js +49 -13
- package/esm/plugin-formatting-codes.js.map +1 -1
- package/esm/plugin-tables.d.ts +4 -2
- package/esm/plugin-tables.js +64 -17
- package/esm/plugin-tables.js.map +1 -1
- package/esm/selectors.d.ts +1 -0
- package/esm/selectors.js +42 -19
- package/esm/selectors.js.map +1 -1
- package/esm/types.d.ts +6 -2
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- package/lib/block-names.d.ts +1 -0
- package/lib/block-names.js +8 -2
- package/lib/blocks-helpers.d.ts +1 -1
- package/lib/blocks-helpers.js +2 -1
- package/lib/exportHtml.js +10 -5
- package/lib/exportMarkdown.js +58 -26
- package/lib/grammarfc.js +7794 -6355
- package/lib/guard.d.ts +5 -0
- package/lib/guard.js +47 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.js +5 -1
- package/lib/pluggableParser.js +3 -0
- package/lib/plugin-data-table.d.ts +1 -1
- package/lib/plugin-data-table.js +12 -10
- package/lib/plugin-formatting-codes.js +49 -13
- package/lib/plugin-tables.d.ts +4 -2
- package/lib/plugin-tables.js +65 -17
- package/lib/selectors.d.ts +1 -0
- package/lib/selectors.js +44 -20
- package/lib/types.d.ts +6 -2
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/schema/AstTree.json +77 -1
- package/schema/PodliteDocument.json +77 -1
package/lib/guard.d.ts
ADDED
package/lib/guard.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
exports.markGuarded = exports.isCovered = void 0;
|
|
7
|
+
const config_1 = __importDefault(require("./helpers/config"));
|
|
8
|
+
// Covered content is marked on the node, not decided at render time. A block
|
|
9
|
+
// carrying :masked or a G<> code can be lifted out of its document by =include,
|
|
10
|
+
// by a data: reference, by an alias expansion or by a selector; the mark travels
|
|
11
|
+
// with the node, so whatever pulls the node out cannot strip the cover off it.
|
|
12
|
+
const GUARD_CODE = 'G';
|
|
13
|
+
const isGuardCode = (node) => node.type === 'fcode' && node.name === GUARD_CODE;
|
|
14
|
+
const isMaskedBlock = (node) => {
|
|
15
|
+
if (node.type !== 'block')
|
|
16
|
+
return false;
|
|
17
|
+
const conf = (0, config_1.default)(node, {});
|
|
18
|
+
return conf.exists('masked') && Boolean(conf.getFirstValue('masked'));
|
|
19
|
+
};
|
|
20
|
+
// The mark says the content is covered; the render mode says whether the cover
|
|
21
|
+
// is applied. Draft shows what production hides, and the mark alone must not
|
|
22
|
+
// override that.
|
|
23
|
+
const isCovered = (node, ctx) => {
|
|
24
|
+
if (ctx?.maskMode)
|
|
25
|
+
return true;
|
|
26
|
+
if (ctx?.renderMode === 'draft')
|
|
27
|
+
return false;
|
|
28
|
+
return Boolean(node?.guarded);
|
|
29
|
+
};
|
|
30
|
+
exports.isCovered = isCovered;
|
|
31
|
+
const markGuarded = (node, inherited = false) => {
|
|
32
|
+
if (!node || typeof node !== 'object')
|
|
33
|
+
return node;
|
|
34
|
+
if (Array.isArray(node)) {
|
|
35
|
+
node.forEach(child => (0, exports.markGuarded)(child, inherited));
|
|
36
|
+
return node;
|
|
37
|
+
}
|
|
38
|
+
const target = node;
|
|
39
|
+
const covered = inherited || target.guarded === true || isGuardCode(target) || isMaskedBlock(target);
|
|
40
|
+
if (covered)
|
|
41
|
+
target.guarded = true;
|
|
42
|
+
if (Array.isArray(target.content))
|
|
43
|
+
target.content.forEach(child => (0, exports.markGuarded)(child, covered));
|
|
44
|
+
return node;
|
|
45
|
+
};
|
|
46
|
+
exports.markGuarded = markGuarded;
|
|
47
|
+
//# sourceMappingURL=guard.js.map
|
package/lib/index.d.ts
CHANGED
|
@@ -87,7 +87,8 @@ export declare const parseFormattingCodes: (text: string, options?: {
|
|
|
87
87
|
export { default as toHtml } from './exportHtml';
|
|
88
88
|
export { default as toMarkdown } from './exportMarkdown';
|
|
89
89
|
export { default as Writer } from './writer';
|
|
90
|
-
export { parseSelector, runSelector } from './selectors';
|
|
90
|
+
export { parseSelector, runSelector, filePathMatches } from './selectors';
|
|
91
|
+
export { markGuarded, isCovered } from './guard';
|
|
91
92
|
export type { SelectorDoc, ParsedSelector } from './selectors';
|
|
92
93
|
export { applyImageBase } from './image-base';
|
|
93
94
|
export { version } from './version';
|
package/lib/index.js
CHANGED
|
@@ -39,7 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
39
39
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
40
|
};
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.version = exports.applyImageBase = exports.runSelector = exports.parseSelector = exports.Writer = exports.toMarkdown = exports.toHtml = exports.parseFormattingCodes = exports.parse = exports.frozenIds = exports.cleanIds = exports.podlitePluggable = exports.toAnyRules = exports.pluginCleanLocation = exports.readLinkConfig = exports.parseAttributes = exports.codeConfigWithDefaults = exports.makeAttrs = exports.toAny = exports.isSemanticBlock = exports.isNamedBlock = exports.makeTransformer = exports.makeInterator = void 0;
|
|
42
|
+
exports.version = exports.applyImageBase = exports.isCovered = exports.markGuarded = exports.filePathMatches = exports.runSelector = exports.parseSelector = exports.Writer = exports.toMarkdown = exports.toHtml = exports.parseFormattingCodes = exports.parse = exports.frozenIds = exports.cleanIds = exports.podlitePluggable = exports.toAnyRules = exports.pluginCleanLocation = exports.readLinkConfig = exports.parseAttributes = exports.codeConfigWithDefaults = exports.makeAttrs = exports.toAny = exports.isSemanticBlock = exports.isNamedBlock = exports.makeTransformer = exports.makeInterator = void 0;
|
|
43
43
|
exports.toAst = toAst;
|
|
44
44
|
exports.getTextContentFromNode = getTextContentFromNode;
|
|
45
45
|
exports.getPodContentFromNode = getPodContentFromNode;
|
|
@@ -226,6 +226,10 @@ Object.defineProperty(exports, "Writer", { enumerable: true, get: function () {
|
|
|
226
226
|
var selectors_1 = require("./selectors");
|
|
227
227
|
Object.defineProperty(exports, "parseSelector", { enumerable: true, get: function () { return selectors_1.parseSelector; } });
|
|
228
228
|
Object.defineProperty(exports, "runSelector", { enumerable: true, get: function () { return selectors_1.runSelector; } });
|
|
229
|
+
Object.defineProperty(exports, "filePathMatches", { enumerable: true, get: function () { return selectors_1.filePathMatches; } });
|
|
230
|
+
var guard_1 = require("./guard");
|
|
231
|
+
Object.defineProperty(exports, "markGuarded", { enumerable: true, get: function () { return guard_1.markGuarded; } });
|
|
232
|
+
Object.defineProperty(exports, "isCovered", { enumerable: true, get: function () { return guard_1.isCovered; } });
|
|
229
233
|
var image_base_1 = require("./image-base");
|
|
230
234
|
Object.defineProperty(exports, "applyImageBase", { enumerable: true, get: function () { return image_base_1.applyImageBase; } });
|
|
231
235
|
var version_1 = require("./version");
|
package/lib/pluggableParser.js
CHANGED
|
@@ -11,6 +11,7 @@ const corePlugins_1 = __importDefault(require("./helpers/corePlugins"));
|
|
|
11
11
|
const configPropagation_1 = require("./helpers/configPropagation");
|
|
12
12
|
const headingNumbering_1 = require("./helpers/headingNumbering");
|
|
13
13
|
const itemNumbering_1 = require("./helpers/itemNumbering");
|
|
14
|
+
const guard_1 = require("./guard");
|
|
14
15
|
var ids_2 = require("./helpers/ids");
|
|
15
16
|
Object.defineProperty(exports, "cleanIds", { enumerable: true, get: function () { return ids_2.cleanIds; } });
|
|
16
17
|
Object.defineProperty(exports, "frozenIds", { enumerable: true, get: function () { return ids_2.frozenIds; } });
|
|
@@ -78,6 +79,7 @@ const podlitePluggable = ({ plugins = {} } = {}) => {
|
|
|
78
79
|
(0, configPropagation_1.propagateConfigDefaults)(resultAfter.interator);
|
|
79
80
|
(0, itemNumbering_1.promoteOrderedLists)(resultAfter.interator);
|
|
80
81
|
(0, headingNumbering_1.attachHeadingNumberPrefix)(resultAfter.interator);
|
|
82
|
+
(0, guard_1.markGuarded)(resultAfter.interator);
|
|
81
83
|
}
|
|
82
84
|
return resultAfter;
|
|
83
85
|
}
|
|
@@ -85,6 +87,7 @@ const podlitePluggable = ({ plugins = {} } = {}) => {
|
|
|
85
87
|
(0, configPropagation_1.propagateConfigDefaults)(result.interator);
|
|
86
88
|
(0, itemNumbering_1.promoteOrderedLists)(result.interator);
|
|
87
89
|
(0, headingNumbering_1.attachHeadingNumberPrefix)(result.interator);
|
|
90
|
+
(0, guard_1.markGuarded)(result.interator);
|
|
88
91
|
}
|
|
89
92
|
return result;
|
|
90
93
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: () => (tree: any) => any;
|
|
1
|
+
declare const _default: (opt?: {}) => (tree: any) => any;
|
|
2
2
|
export default _default;
|
package/lib/plugin-data-table.js
CHANGED
|
@@ -43,7 +43,8 @@ function resolveColumnIndex(col, headerRow, maxCols) {
|
|
|
43
43
|
}
|
|
44
44
|
const idx = headerRow.indexOf(col);
|
|
45
45
|
if (idx === -1) {
|
|
46
|
-
|
|
46
|
+
const names = headerRow.map(h => `"${h}"`).join(', ');
|
|
47
|
+
throw new Error(`column name "${col}" not found in header; header has ${names}`);
|
|
47
48
|
}
|
|
48
49
|
return idx;
|
|
49
50
|
}
|
|
@@ -65,7 +66,7 @@ function applyRename(rows, rename, hasHeader) {
|
|
|
65
66
|
}
|
|
66
67
|
return [headerRow, ...rows.slice(1)];
|
|
67
68
|
}
|
|
68
|
-
function processDataTable(node, tree) {
|
|
69
|
+
function processDataTable(node, tree, report) {
|
|
69
70
|
const attrs = (0, config_1.default)(node, {});
|
|
70
71
|
const srcRaw = attrs.getFirstValue('src');
|
|
71
72
|
const mimeAttr = attrs.getFirstValue('mime-type');
|
|
@@ -91,7 +92,7 @@ function processDataTable(node, tree) {
|
|
|
91
92
|
if (scheme === 'data') {
|
|
92
93
|
const dataBlock = (0, plugin_tables_1.findDataBlockByKey)(tree, target);
|
|
93
94
|
if (!dataBlock) {
|
|
94
|
-
|
|
95
|
+
report('table-source-unreadable', `no =data block found for data:${target}, table rendered as empty`, node);
|
|
95
96
|
return { ...node, name: 'table', content: [] };
|
|
96
97
|
}
|
|
97
98
|
if (mimeAttr) {
|
|
@@ -99,7 +100,7 @@ function processDataTable(node, tree) {
|
|
|
99
100
|
}
|
|
100
101
|
const dataMime = (0, config_1.default)(dataBlock, {}).getFirstValue('mime-type');
|
|
101
102
|
if (!dataMime) {
|
|
102
|
-
|
|
103
|
+
report('table-source-unreadable', `=data block ${target} has no :mime-type, table rendered as empty`, node);
|
|
103
104
|
return { ...node, name: 'table', content: [] };
|
|
104
105
|
}
|
|
105
106
|
const parsed = (0, plugin_tables_1.parseMimeType)(dataMime);
|
|
@@ -123,12 +124,12 @@ function processDataTable(node, tree) {
|
|
|
123
124
|
const isCsv = mimeType === 'text/csv';
|
|
124
125
|
const isTsv = mimeType === 'text/tab-separated-values';
|
|
125
126
|
if (!isCsv && !isTsv) {
|
|
126
|
-
|
|
127
|
+
report('table-source-unreadable', `unsupported mime-type ${mimeType}, table rendered as empty`, node);
|
|
127
128
|
return { ...node, name: 'table', content: [] };
|
|
128
129
|
}
|
|
129
130
|
let rows = isCsv ? (0, plugin_tables_1.parseCsv)(csvText) : (0, plugin_tables_1.parseTsv)(csvText);
|
|
130
131
|
if (rows.length === 0) {
|
|
131
|
-
|
|
132
|
+
report('table-source-unreadable', `${isCsv ? 'CSV' : 'TSV'} source has no rows, table rendered as empty`, node);
|
|
132
133
|
return { ...node, name: 'table', content: [] };
|
|
133
134
|
}
|
|
134
135
|
if (columns) {
|
|
@@ -142,16 +143,17 @@ function processDataTable(node, tree) {
|
|
|
142
143
|
name: 'table',
|
|
143
144
|
content: (0, plugin_tables_1.csvToTableContent)(rows, hasHeader, attrs.getAllValues('allow')),
|
|
144
145
|
};
|
|
145
|
-
return (0, plugin_tables_1.normalizeCellCounts)(filledNode, 'data-table');
|
|
146
|
+
return (0, plugin_tables_1.normalizeCellCounts)(filledNode, 'data-table', report);
|
|
146
147
|
}
|
|
147
|
-
exports.default = () => tree => {
|
|
148
|
+
exports.default = (opt = {}) => tree => {
|
|
149
|
+
const report = (0, plugin_tables_1.makeTableReport)(opt);
|
|
148
150
|
const transformer = (0, makeTransformer_1.default)({
|
|
149
151
|
'data-table': node => {
|
|
150
152
|
try {
|
|
151
|
-
return processDataTable(node, tree);
|
|
153
|
+
return processDataTable(node, tree, report);
|
|
152
154
|
}
|
|
153
155
|
catch (err) {
|
|
154
|
-
|
|
156
|
+
report('table-source-unreadable', err.message, node);
|
|
155
157
|
return { ...node, name: 'table', content: [] };
|
|
156
158
|
}
|
|
157
159
|
},
|
|
@@ -41,49 +41,85 @@ const makeTransformer_1 = __importDefault(require("./helpers/makeTransformer"));
|
|
|
41
41
|
const makeTransformer_2 = require("./helpers/makeTransformer");
|
|
42
42
|
const config_1 = __importDefault(require("./helpers/config"));
|
|
43
43
|
const parseAttributes_1 = require("./helpers/parseAttributes");
|
|
44
|
+
// `=config C<> :allow<I>` names a markup code, not a block: the trailing angles
|
|
45
|
+
// are the marker, and what it declares belongs to the code they name
|
|
46
|
+
const codeConfigOwner = (name) => typeof name === 'string' && /^[A-Z]<>$/.test(name) ? name[0] : null;
|
|
47
|
+
const collectAllowedIn = (content, inherited) => {
|
|
48
|
+
if (!Array.isArray(content))
|
|
49
|
+
return inherited;
|
|
50
|
+
let map = inherited;
|
|
51
|
+
for (const child of content) {
|
|
52
|
+
if (!child || typeof child !== 'object')
|
|
53
|
+
continue;
|
|
54
|
+
const node = child;
|
|
55
|
+
if (node.type !== 'config')
|
|
56
|
+
continue;
|
|
57
|
+
const owner = codeConfigOwner(node.name);
|
|
58
|
+
if (!owner)
|
|
59
|
+
continue;
|
|
60
|
+
if (map === inherited)
|
|
61
|
+
map = { ...inherited };
|
|
62
|
+
map[owner] = (0, config_1.default)(node, {}).getAllValues('allow');
|
|
63
|
+
}
|
|
64
|
+
return map;
|
|
65
|
+
};
|
|
44
66
|
const middle = () => tree => {
|
|
45
67
|
const transformerBlocks = (0, makeTransformer_1.default)({
|
|
46
68
|
':para': (n, ctx, visiter) => {
|
|
69
|
+
const allowedIn = ctx.allowedIn;
|
|
47
70
|
return (0, makeTransformer_1.default)({
|
|
48
71
|
':text': (n, ctx) => {
|
|
49
|
-
return fcparser.parse(n.value, { parseAttributes: parseAttributes_1.parseAttributes });
|
|
72
|
+
return fcparser.parse(n.value, { allowedIn, parseAttributes: parseAttributes_1.parseAttributes });
|
|
50
73
|
},
|
|
51
74
|
':verbatim': (n, ctx) => {
|
|
52
|
-
return fcparser.parse(n.value, { parseAttributes: parseAttributes_1.parseAttributes });
|
|
75
|
+
return fcparser.parse(n.value, { allowedIn, parseAttributes: parseAttributes_1.parseAttributes });
|
|
53
76
|
},
|
|
54
77
|
})(n, { ...ctx });
|
|
55
78
|
return n;
|
|
56
79
|
},
|
|
57
80
|
':block': (n, ctx, visiter) => {
|
|
81
|
+
// a block is a lexical scope: a code configured inside it stays inside
|
|
82
|
+
const allowedIn = collectAllowedIn('content' in n ? n.content : undefined, ctx.allowedIn || {});
|
|
58
83
|
// only =pod may have childs blocks
|
|
59
84
|
if ('name' in n && n.name === 'pod')
|
|
60
85
|
return {
|
|
61
86
|
...n,
|
|
62
|
-
content: visiter(n.content, ctx, visiter),
|
|
87
|
+
content: visiter(n.content, { ...ctx, allowedIn }, visiter),
|
|
63
88
|
};
|
|
64
89
|
const conf = (0, config_1.default)(n, ctx);
|
|
65
90
|
const name = 'name' in n ? n.name : '';
|
|
66
91
|
// Blocks whose content is verbatim by default — fcode parsing only
|
|
67
92
|
// kicks in when :allow opts in (per spec, "Formatting within code blocks").
|
|
68
93
|
const isVerbatimDefault = ['code', 'data', 'markdown', 'picture', 'formula'].includes(name);
|
|
69
|
-
const allowValues = conf.getAllValues('allow');
|
|
70
94
|
if ((0, makeTransformer_2.isNamedBlock)(name))
|
|
71
95
|
return n;
|
|
96
|
+
// A table owns rows, and the text sits in the cells, so :allow written on
|
|
97
|
+
// the table reaches them. The nearest declaration wins: cell, row, table.
|
|
98
|
+
const inheritsAllow = name === 'row' || name === 'cell';
|
|
99
|
+
const declared = conf.exists('allow')
|
|
100
|
+
? conf.getAllValues('allow')
|
|
101
|
+
: inheritsAllow
|
|
102
|
+
? ctx.allowFromTable
|
|
103
|
+
: undefined;
|
|
104
|
+
const allowValues = declared || [];
|
|
105
|
+
const passesAllow = name === 'table' || name === 'row' ? declared : ctx.allowFromTable;
|
|
72
106
|
if (isVerbatimDefault && allowValues.length === 0)
|
|
73
107
|
return n;
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const allowed = allowValues.sort();
|
|
108
|
+
// declared empty means no code acts on this text; nested blocks still get
|
|
109
|
+
// their turn, since a cell may declare a set of its own
|
|
110
|
+
const literal = inheritsAllow && declared !== undefined && declared.length === 0;
|
|
111
|
+
const allowed = [...allowValues].sort();
|
|
112
|
+
const inner = { ...ctx, allowedIn, allowFromTable: passesAllow };
|
|
78
113
|
const transformer = (0, makeTransformer_1.default)({
|
|
79
|
-
':verbatim': (
|
|
80
|
-
':text': (
|
|
81
|
-
':block': (
|
|
114
|
+
':verbatim': (node, ctx) => literal ? node : fcparser.parse(node.value, { allowed, allowedIn, parseAttributes: parseAttributes_1.parseAttributes }),
|
|
115
|
+
':text': (node, ctx) => literal ? node : fcparser.parse(node.value, { allowed, allowedIn, parseAttributes: parseAttributes_1.parseAttributes }),
|
|
116
|
+
':block': (node, ctx) => transformerBlocks(node, { ...ctx, allowedIn, allowFromTable: passesAllow }),
|
|
82
117
|
});
|
|
83
|
-
return { ...n, content: transformer(n.content,
|
|
118
|
+
return { ...n, content: transformer(n.content, inner) };
|
|
84
119
|
},
|
|
85
120
|
});
|
|
86
|
-
|
|
121
|
+
// a document needs no enclosing block, so the top level is a scope of its own
|
|
122
|
+
return transformerBlocks(tree, { allowedIn: collectAllowedIn(tree, {}) });
|
|
87
123
|
};
|
|
88
124
|
exports.default = middle;
|
|
89
125
|
//# sourceMappingURL=plugin-formatting-codes.js.map
|
package/lib/plugin-tables.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export declare function parseMimeType(raw: any): {
|
|
|
7
7
|
export declare function findDataBlockByKey(tree: any, key: any): any;
|
|
8
8
|
export declare function extractDataText(dataNode: any): any;
|
|
9
9
|
export declare function csvToTableContent(csvRows: any, hasHeader?: boolean, allow?: string[]): any;
|
|
10
|
-
export
|
|
11
|
-
declare
|
|
10
|
+
export type TableReport = (code: string, message: string, node: any) => void;
|
|
11
|
+
export declare function makeTableReport(opt: any): TableReport;
|
|
12
|
+
export declare function normalizeCellCounts(tableNode: any, source?: string, report?: TableReport): any;
|
|
13
|
+
declare const _default: (opt?: {}) => (tree: any) => any;
|
|
12
14
|
export default _default;
|
package/lib/plugin-tables.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.parseMimeType = parseMimeType;
|
|
|
9
9
|
exports.findDataBlockByKey = findDataBlockByKey;
|
|
10
10
|
exports.extractDataText = extractDataText;
|
|
11
11
|
exports.csvToTableContent = csvToTableContent;
|
|
12
|
+
exports.makeTableReport = makeTableReport;
|
|
12
13
|
exports.normalizeCellCounts = normalizeCellCounts;
|
|
13
14
|
/**
|
|
14
15
|
* Plugin for fill term's for defn. From S26:
|
|
@@ -216,16 +217,27 @@ function csvToTableContent(csvRows, hasHeader = false, allow = []) {
|
|
|
216
217
|
return buildRowBlock(cells, hasHeader && i === 0);
|
|
217
218
|
});
|
|
218
219
|
}
|
|
219
|
-
|
|
220
|
+
function makeTableReport(opt) {
|
|
221
|
+
const diagnostics = opt && Array.isArray(opt.diagnostics) ? opt.diagnostics : null;
|
|
222
|
+
return (code, message, node) => {
|
|
223
|
+
const location = node && node.location;
|
|
224
|
+
if (!diagnostics || !location)
|
|
225
|
+
return;
|
|
226
|
+
const seen = diagnostics.some(d => d.message === message && d.location.start.offset === location.start.offset);
|
|
227
|
+
if (!seen)
|
|
228
|
+
diagnostics.push({ severity: 'warning', code, message, location });
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const noReport = () => { };
|
|
220
232
|
// Rule 2 — table-level cell count validation. Pad short rows with empty
|
|
221
|
-
// cells; truncate long rows.
|
|
233
|
+
// cells; truncate long rows. Report whenever a row is changed.
|
|
222
234
|
// Expected count is taken from the `:header` row if present, otherwise from
|
|
223
235
|
// the row with the maximum cell count.
|
|
224
236
|
//
|
|
225
237
|
// Skipped when any cell uses `:colspan` or `:rowspan`: a spanning cell
|
|
226
238
|
// occupies multiple columns, so naive cell counting would misreport row
|
|
227
239
|
// width and drop legitimate spanned cells.
|
|
228
|
-
function normalizeCellCounts(tableNode, source = 'table') {
|
|
240
|
+
function normalizeCellCounts(tableNode, source = 'table', report = noReport) {
|
|
229
241
|
if (!tableNode || !Array.isArray(tableNode.content))
|
|
230
242
|
return tableNode;
|
|
231
243
|
const rows = tableNode.content.filter(c => c && c.type === 'block' && c.name === 'row');
|
|
@@ -252,13 +264,13 @@ function normalizeCellCounts(tableNode, source = 'table') {
|
|
|
252
264
|
const padding = [];
|
|
253
265
|
for (let i = cells.length; i < expected; i++)
|
|
254
266
|
padding.push(buildCellBlock(''));
|
|
255
|
-
|
|
267
|
+
report('table-row-cells', `${source} row has ${cells.length} of ${expected} cells, padded with ${padding.length} empty. A row continued on the next line needs a blank line before the next row`, tableNode);
|
|
256
268
|
mutated = true;
|
|
257
269
|
return { ...child, content: [...child.content, ...padding] };
|
|
258
270
|
}
|
|
259
271
|
// cells.length > expected → truncate
|
|
260
272
|
const dropped = cells.length - expected;
|
|
261
|
-
|
|
273
|
+
report('table-row-cells', `${source} row has ${cells.length} of ${expected} cells, dropped ${dropped}`, tableNode);
|
|
262
274
|
mutated = true;
|
|
263
275
|
// Keep non-cell entries (e.g. blanklines) and the first `expected` cells
|
|
264
276
|
let keepCells = expected;
|
|
@@ -282,7 +294,7 @@ function normalizeCellCounts(tableNode, source = 'table') {
|
|
|
282
294
|
// for visible separators (`|` / `+`) surrounded by whitespace; lines without
|
|
283
295
|
// any visible separator fall back to whitespace separation. Warns when more
|
|
284
296
|
// than one separator type is observed within a single table.
|
|
285
|
-
function detectMixedSeparators(lines) {
|
|
297
|
+
function detectMixedSeparators(lines, report = noReport, node = null) {
|
|
286
298
|
const seen = new Set();
|
|
287
299
|
for (const line of lines) {
|
|
288
300
|
if (!line || typeof line !== 'string')
|
|
@@ -297,7 +309,7 @@ function detectMixedSeparators(lines) {
|
|
|
297
309
|
break;
|
|
298
310
|
}
|
|
299
311
|
if (seen.size > 1) {
|
|
300
|
-
|
|
312
|
+
report('table-mixed-separators', `table mixes separator styles: ${Array.from(seen).join(', ')}. Keep one`, node);
|
|
301
313
|
}
|
|
302
314
|
}
|
|
303
315
|
// Rule 4 — replace =table with =code block. Used when a referenced =data
|
|
@@ -448,9 +460,41 @@ const wrapImplicitCells = rowNode => {
|
|
|
448
460
|
});
|
|
449
461
|
return { ...rowNode, content: wrapped };
|
|
450
462
|
};
|
|
463
|
+
// An abbreviated `=for row` owns its own paragraph, so cells written under it
|
|
464
|
+
// stay at table level and both the row and the cells lose their meaning. They
|
|
465
|
+
// are attached to the row above, and the author is told, per the same recovery
|
|
466
|
+
// contract that pads and truncates rows.
|
|
467
|
+
const attachOrphanCells = (tableNode, report = noReport) => {
|
|
468
|
+
const content = tableNode.content;
|
|
469
|
+
if (!Array.isArray(content))
|
|
470
|
+
return tableNode;
|
|
471
|
+
if (!content.some(c => c && c.type === 'block' && c.name === 'cell'))
|
|
472
|
+
return tableNode;
|
|
473
|
+
const out = [];
|
|
474
|
+
let openRow = null;
|
|
475
|
+
for (const child of content) {
|
|
476
|
+
if (child && child.type === 'block' && child.name === 'row') {
|
|
477
|
+
openRow = { ...child, content: Array.isArray(child.content) ? [...child.content] : [] };
|
|
478
|
+
out.push(openRow);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (child && child.type === 'block' && child.name === 'cell') {
|
|
482
|
+
if (!openRow) {
|
|
483
|
+
openRow = { type: 'block', name: 'row', margin: child.margin || '', content: [] };
|
|
484
|
+
out.push(openRow);
|
|
485
|
+
}
|
|
486
|
+
openRow.content.push(child);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
out.push(child);
|
|
490
|
+
}
|
|
491
|
+
report('table-cell-outside-row', 'a cell written outside a row was attached to the row above it; an abbreviated =for row holds only its own paragraph, so write =begin row and =end row around the cells', tableNode);
|
|
492
|
+
return { ...tableNode, content: out };
|
|
493
|
+
};
|
|
451
494
|
const isStructured = tableNode => Array.isArray(tableNode.content) &&
|
|
452
495
|
tableNode.content.some(c => c && c.type === 'block' && (c.name === 'row' || c.name === 'cell'));
|
|
453
|
-
exports.default = () => tree => {
|
|
496
|
+
exports.default = (opt = {}) => tree => {
|
|
497
|
+
const report = makeTableReport(opt);
|
|
454
498
|
const transformer = (0, makeTransformer_1.default)({
|
|
455
499
|
table: (node, ctx, visiter) => {
|
|
456
500
|
// CSV/data source reference (spec §1672):
|
|
@@ -461,9 +505,12 @@ exports.default = () => tree => {
|
|
|
461
505
|
const dataBlock = findDataBlockByKey(tree, ref.target);
|
|
462
506
|
if (!dataBlock) {
|
|
463
507
|
// Rule 4: source not found → empty table (still a =table block)
|
|
464
|
-
|
|
508
|
+
report('table-source-unreadable', `no =data block found for data:${ref.target}, table rendered as empty`, node);
|
|
465
509
|
return { ...node, content: [] };
|
|
466
510
|
}
|
|
511
|
+
// whatever is built out of a covered =data block is covered as well,
|
|
512
|
+
// otherwise the table is a way to read what the cover hides
|
|
513
|
+
const carryCover = built => (0, config_1.default)(dataBlock, {}).getFirstValue('masked') ? { ...built, guarded: true } : built;
|
|
467
514
|
const rawMime = (0, config_1.default)(dataBlock, {}).getFirstValue('mime-type');
|
|
468
515
|
const { type: mimeType, params: mimeParams } = parseMimeType(rawMime);
|
|
469
516
|
const isCsv = mimeType === 'text/csv';
|
|
@@ -472,22 +519,23 @@ exports.default = () => tree => {
|
|
|
472
519
|
const text = extractDataText(dataBlock);
|
|
473
520
|
const rows = isCsv ? parseCsv(text) : parseTsv(text);
|
|
474
521
|
if (rows.length === 0) {
|
|
475
|
-
|
|
522
|
+
report('table-source-unreadable', `${isCsv ? 'CSV' : 'TSV'} source data:${ref.target} has no rows, table rendered as empty`, node);
|
|
476
523
|
return { ...node, content: [] };
|
|
477
524
|
}
|
|
478
525
|
const hasHeader = mimeParams.header === 'present';
|
|
479
526
|
const allow = (0, config_1.default)(node, {}).getAllValues('allow');
|
|
480
527
|
const filledNode = { ...node, content: csvToTableContent(rows, hasHeader, allow) };
|
|
481
|
-
return normalizeCellCounts(filledNode, `table data:${ref.target}
|
|
528
|
+
return carryCover(normalizeCellCounts(filledNode, `table data:${ref.target}`, report));
|
|
482
529
|
}
|
|
483
530
|
// Rule 4: source not tabular → render as code block so content remains visible
|
|
484
|
-
|
|
485
|
-
return buildCodeFromDataBlock(node, dataBlock);
|
|
531
|
+
report('table-source-unreadable', `=data :key<${ref.target}> has non-tabular mime-type ${rawMime || '(none)'}, rendered as =code`, node);
|
|
532
|
+
return carryCover(buildCodeFromDataBlock(node, dataBlock));
|
|
486
533
|
}
|
|
487
534
|
// structured mode: transform row children (wrap implicit cells), then
|
|
488
535
|
// apply Rule 2 cell count normalization.
|
|
489
536
|
if (isStructured(node)) {
|
|
490
|
-
const
|
|
537
|
+
const attached = attachOrphanCells(node, report);
|
|
538
|
+
const transformedContent = (attached.content || []).map(c => {
|
|
491
539
|
if (c && c.name === 'row')
|
|
492
540
|
return wrapImplicitCells(c);
|
|
493
541
|
return c;
|
|
@@ -495,7 +543,7 @@ exports.default = () => tree => {
|
|
|
495
543
|
// a matched rule replaces the node without descending, so recurse
|
|
496
544
|
// here or tables nested inside cells keep their raw text rows
|
|
497
545
|
const recursed = visiter ? visiter(transformedContent, ctx) : transformedContent;
|
|
498
|
-
return normalizeCellCounts({ ...
|
|
546
|
+
return normalizeCellCounts({ ...attached, content: recursed }, 'table', report);
|
|
499
547
|
}
|
|
500
548
|
let rows = [];
|
|
501
549
|
const collectValues = row => {
|
|
@@ -521,7 +569,7 @@ exports.default = () => tree => {
|
|
|
521
569
|
const lines = flattenDeep(rows.map(splitToLines));
|
|
522
570
|
const separators = flattenDeep(seps.map(splitToLines));
|
|
523
571
|
// Rule 3: warn on mixed separator types within a single table
|
|
524
|
-
detectMixedSeparators(lines);
|
|
572
|
+
detectMixedSeparators(lines, report, node);
|
|
525
573
|
// collect text rows
|
|
526
574
|
let textRows = [];
|
|
527
575
|
(0, makeTransformer_1.default)({
|
|
@@ -560,7 +608,7 @@ exports.default = () => tree => {
|
|
|
560
608
|
},
|
|
561
609
|
'head:text': head => makeHeaderRow(splitToCells(head.value).map(makeCell)),
|
|
562
610
|
})(node);
|
|
563
|
-
return normalizeCellCounts(res, 'table');
|
|
611
|
+
return normalizeCellCounts(res, 'table', report);
|
|
564
612
|
},
|
|
565
613
|
});
|
|
566
614
|
return transformer(tree, {});
|
package/lib/selectors.d.ts
CHANGED
|
@@ -31,4 +31,5 @@ export type ParsedSelector = {
|
|
|
31
31
|
patterns: Pattern[];
|
|
32
32
|
};
|
|
33
33
|
export declare const parseSelector: (selector: string) => ParsedSelector | undefined;
|
|
34
|
+
export declare const filePathMatches: (docFile: string, target: string) => boolean;
|
|
34
35
|
export declare const runSelector: <T extends SelectorDoc>(selector: string, docs: T[]) => T[] | PodNode[];
|
package/lib/selectors.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runSelector = exports.parseSelector = void 0;
|
|
3
|
+
exports.runSelector = exports.filePathMatches = exports.parseSelector = void 0;
|
|
4
4
|
const index_1 = require("./index");
|
|
5
|
+
const parseAttributes_1 = require("./helpers/parseAttributes");
|
|
5
6
|
// --- predicate parser ---------------------------------------------------
|
|
6
7
|
const isIdentStart = (c) => /[a-zA-Z_]/.test(c);
|
|
7
8
|
const isIdentCont = (c) => /[a-zA-Z0-9_-]/.test(c);
|
|
@@ -229,6 +230,39 @@ const parseSelector = (selector) => {
|
|
|
229
230
|
return undefined;
|
|
230
231
|
};
|
|
231
232
|
exports.parseSelector = parseSelector;
|
|
233
|
+
// The operand is read by the same grammar as a declaration: the delimiters
|
|
234
|
+
// around it decide whether it is a string, a list or a number. Reading it as
|
|
235
|
+
// raw text would put values out of reach that a document can hold — a quoted
|
|
236
|
+
// string among them.
|
|
237
|
+
const readOperand = (raw) => {
|
|
238
|
+
const [item] = (0, parseAttributes_1.parseAttributes)(`:x<${raw}>`);
|
|
239
|
+
return item ? { value: item.value, type: item.type } : undefined;
|
|
240
|
+
};
|
|
241
|
+
// The declared value with its kind kept. makeAttrs flattens a list into the
|
|
242
|
+
// surrounding values, which loses the very thing equality compares.
|
|
243
|
+
const declaredValue = (node, name, ctx) => {
|
|
244
|
+
const anyNode = node;
|
|
245
|
+
const own = (Array.isArray(anyNode.config) ? anyNode.config : []).find(c => c && c.name === name);
|
|
246
|
+
if (own)
|
|
247
|
+
return { value: own.value, type: own.type };
|
|
248
|
+
const configured = (anyNode.name && ctx.config?.[anyNode.name]) || [];
|
|
249
|
+
const inherited = configured.find(c => c && c.name === name);
|
|
250
|
+
return inherited ? { value: inherited.value, type: inherited.type } : undefined;
|
|
251
|
+
};
|
|
252
|
+
const sameScalar = (a, b) => typeof a === typeof b && a === b;
|
|
253
|
+
// A list equals another list when their elements match one by one, in order; a
|
|
254
|
+
// value of one kind never equals a value of another.
|
|
255
|
+
const sameValue = (a, b) => {
|
|
256
|
+
if (Array.isArray(a.value) || Array.isArray(b.value)) {
|
|
257
|
+
if (!Array.isArray(a.value) || !Array.isArray(b.value))
|
|
258
|
+
return false;
|
|
259
|
+
return a.value.length === b.value.length && a.value.every((item, i) => sameScalar(item, b.value[i]));
|
|
260
|
+
}
|
|
261
|
+
return sameScalar(a.value, b.value);
|
|
262
|
+
};
|
|
263
|
+
// Membership runs over the values as declared. A string is one value, even
|
|
264
|
+
// when it holds spaces, so a word taken from its middle is not a member.
|
|
265
|
+
const valuesOf = (typed) => (Array.isArray(typed.value) ? typed.value : [typed.value]);
|
|
232
266
|
const matchCondition = (node, cond, ctx) => {
|
|
233
267
|
const attrs = (0, index_1.makeAttrs)(node, ctx);
|
|
234
268
|
const exists = attrs.exists(cond.attrName);
|
|
@@ -244,30 +278,19 @@ const matchCondition = (node, cond, ctx) => {
|
|
|
244
278
|
return !exists;
|
|
245
279
|
}
|
|
246
280
|
}
|
|
281
|
+
const declared = declaredValue(node, cond.attrName, ctx);
|
|
282
|
+
const operand = readOperand(cond.valueSpec.value);
|
|
247
283
|
if (cond.valueSpec.kind === 'angle') {
|
|
248
|
-
if (!exists)
|
|
284
|
+
if (!exists || !declared || !operand)
|
|
249
285
|
return false;
|
|
250
|
-
const equal =
|
|
286
|
+
const equal = sameValue(declared, operand);
|
|
251
287
|
return cond.modifier === '!' ? !equal : equal;
|
|
252
288
|
}
|
|
253
289
|
if (cond.valueSpec.kind === 'contains') {
|
|
254
|
-
if (!exists)
|
|
290
|
+
if (!exists || !declared || !operand)
|
|
255
291
|
return false;
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// split string values on whitespace/comma to recover list shape.
|
|
259
|
-
const tokens = [];
|
|
260
|
-
for (const v of attrs.getAllValues(cond.attrName)) {
|
|
261
|
-
if (typeof v === 'string') {
|
|
262
|
-
for (const t of v.split(/[\s,]+/))
|
|
263
|
-
if (t)
|
|
264
|
-
tokens.push(t);
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
tokens.push(String(v));
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
const present = tokens.includes(cond.valueSpec.value);
|
|
292
|
+
const values = valuesOf(declared);
|
|
293
|
+
const present = valuesOf(operand).every(wanted => values.some(held => sameScalar(held, wanted)));
|
|
271
294
|
return cond.modifier === '!' ? !present : present;
|
|
272
295
|
}
|
|
273
296
|
return false;
|
|
@@ -388,6 +411,7 @@ const filePathMatches = (docFile, target) => {
|
|
|
388
411
|
}
|
|
389
412
|
return a === b || a.endsWith('/' + b) || b.endsWith('/' + a);
|
|
390
413
|
};
|
|
414
|
+
exports.filePathMatches = filePathMatches;
|
|
391
415
|
const getDocIDs = (doc) => {
|
|
392
416
|
const ids = [];
|
|
393
417
|
(0, index_1.getFromTree)(doc.node, 'NAME', 'TITLE').forEach(block => {
|
|
@@ -421,7 +445,7 @@ const runSelector = (selector, docs) => {
|
|
|
421
445
|
matchedDocs = docs.filter(doc => getDocIDs(doc).includes(document));
|
|
422
446
|
}
|
|
423
447
|
else if (scheme === 'file' && document) {
|
|
424
|
-
matchedDocs = docs.filter(doc => filePathMatches(doc.file, document));
|
|
448
|
+
matchedDocs = docs.filter(doc => (0, exports.filePathMatches)(doc.file, document));
|
|
425
449
|
}
|
|
426
450
|
else if (scheme && scheme !== 'doc' && scheme !== 'file') {
|
|
427
451
|
return [];
|
package/lib/types.d.ts
CHANGED
|
@@ -98,6 +98,7 @@ export interface Image {
|
|
|
98
98
|
type: 'image';
|
|
99
99
|
src: string;
|
|
100
100
|
alt?: string;
|
|
101
|
+
link?: string;
|
|
101
102
|
}
|
|
102
103
|
export interface Toc {
|
|
103
104
|
type: 'toc';
|
|
@@ -139,7 +140,7 @@ export interface RootBlock extends Omit<Block, 'location'> {
|
|
|
139
140
|
}
|
|
140
141
|
export interface ParseDiagnostic {
|
|
141
142
|
severity: 'warning';
|
|
142
|
-
code: 'value-unreadable' | 'directive-unreadable';
|
|
143
|
+
code: 'value-unreadable' | 'directive-unreadable' | 'table-row-cells' | 'table-cell-outside-row' | 'table-mixed-separators' | 'table-source-unreadable';
|
|
143
144
|
message: string;
|
|
144
145
|
location: Location;
|
|
145
146
|
}
|
|
@@ -245,10 +246,12 @@ export interface Ambient {
|
|
|
245
246
|
export interface Verbatim {
|
|
246
247
|
type: 'verbatim';
|
|
247
248
|
value: string;
|
|
249
|
+
guarded?: boolean;
|
|
248
250
|
}
|
|
249
251
|
export interface Text {
|
|
250
252
|
type: 'text';
|
|
251
253
|
value: string;
|
|
254
|
+
guarded?: boolean;
|
|
252
255
|
}
|
|
253
256
|
export interface Para {
|
|
254
257
|
type: 'para';
|
|
@@ -308,6 +311,7 @@ export interface Block {
|
|
|
308
311
|
margin: string;
|
|
309
312
|
config?: Array<ConfigItem | BrokenConfigItem>;
|
|
310
313
|
id?: string;
|
|
314
|
+
guarded?: boolean;
|
|
311
315
|
}
|
|
312
316
|
export interface BlockPod extends Block {
|
|
313
317
|
name: 'pod';
|
|
@@ -364,7 +368,7 @@ export interface BlockTable extends Omit<Block, 'content'> {
|
|
|
364
368
|
export interface TableCell {
|
|
365
369
|
name: 'cell';
|
|
366
370
|
type: 'block';
|
|
367
|
-
content: Array<
|
|
371
|
+
content: Array<PodNode>;
|
|
368
372
|
}
|
|
369
373
|
export interface TableRow {
|
|
370
374
|
name: 'row';
|