@markup-carve/pandoc-carve 0.1.6 → 0.1.7
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/README.md +8 -1
- package/dist/diagnostics.js +7 -4
- package/dist/reverse.js +43 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npm install @markup-carve/pandoc-carve
|
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
The CLI requires Pandoc 3.x on `PATH` for formats other than JSON. CI currently
|
|
23
|
-
tests Pandoc 3.11. Pandoc is not bundled.
|
|
23
|
+
tests Pandoc 3.11 and 3.10.2. Pandoc is not bundled.
|
|
24
24
|
|
|
25
25
|
## CLI
|
|
26
26
|
|
|
@@ -105,6 +105,13 @@ them. Highlights include:
|
|
|
105
105
|
- raw target content only survives in matching writers;
|
|
106
106
|
- comments and attribute placement require round-trip metadata.
|
|
107
107
|
|
|
108
|
+
These limits are measured rather than asserted.
|
|
109
|
+
[pandoc-format-fidelity](https://github.com/markup-carve/pandoc-format-fidelity) runs Carve
|
|
110
|
+
source through this bridge and back for every writer it lists, and publishes the verdicts per
|
|
111
|
+
format: `results/carve-rt.json` holds the Carve round-trip lane, and
|
|
112
|
+
[the report](https://github.com/markup-carve/pandoc-format-fidelity/blob/main/docs/index.html)
|
|
113
|
+
covers every pandoc writer and reader.
|
|
114
|
+
|
|
108
115
|
Dangerous URL schemes such as `javascript:` are blanked and reported. The
|
|
109
116
|
[URL policy](docs/reference.md#dangerous-url-schemes-are-blanked) documents the
|
|
110
117
|
exact behavior. The [mapping and limitations reference](docs/reference.md#what-maps-to-what)
|
package/dist/diagnostics.js
CHANGED
|
@@ -10,7 +10,7 @@ const RULES = [
|
|
|
10
10
|
{ test: /^block: pandoc node/, code: 'unsupported-pandoc-block', class: 'unsupported' },
|
|
11
11
|
{ test: /^citation: .*typed locator/, code: 'citation-locator-flattened', class: 'normalized' },
|
|
12
12
|
{ test: /^citation: .*suppresses its author/, code: 'citation-mode-normalized', class: 'normalized' },
|
|
13
|
-
{ test: /^Cite mixes /, code: 'citation-mode-normalized', class: '
|
|
13
|
+
{ test: /^Cite mixes /, code: 'citation-mode-normalized', class: 'degraded' },
|
|
14
14
|
{ test: /^Cite mapped /, code: 'citation-bibliography-not-emitted', class: 'degraded' },
|
|
15
15
|
{ test: /^SmallCaps /, code: 'smallcaps-degraded', class: 'degraded' },
|
|
16
16
|
{ test: /^short caption:/, code: 'short-caption-source-unavailable', class: 'unsupported' },
|
|
@@ -40,16 +40,19 @@ const RULES = [
|
|
|
40
40
|
{ test: /^list-table: structure/, code: 'list-table-structure-degraded', class: 'degraded' },
|
|
41
41
|
{ test: /^list-table: the short caption/, code: 'list-table-short-caption-dropped', class: 'lossy' },
|
|
42
42
|
{ test: /^list-table: a body group's attributes/, code: 'list-table-body-attributes-dropped', class: 'lossy' },
|
|
43
|
-
{ test: /^list-table:
|
|
44
|
-
{ test: /^list-table: the body groups
|
|
43
|
+
{ test: /^list-table: a row's attributes/, code: 'list-table-row-attributes-dropped', class: 'lossy' },
|
|
44
|
+
{ test: /^list-table: the table's .* body groups/, code: 'list-table-body-groups-merged', class: 'degraded' },
|
|
45
|
+
{ test: /^list-table: the body groups disagree/, code: 'list-table-row-heads-normalized', class: 'degraded' },
|
|
45
46
|
{ test: /^list-table: rowspan/, code: 'list-table-rowspan-clipped', class: 'lossy' },
|
|
46
47
|
{ test: /^table: a row header outside/, code: 'table-row-head-outside-leading-run', class: 'lossy' },
|
|
47
48
|
{ test: /^table: attributes on/, code: 'table-continuation-attributes-dropped', class: 'lossy' },
|
|
48
49
|
{ test: /^table: colspan continuation/, code: 'table-colspan-origin-missing', class: 'degraded' },
|
|
49
50
|
{ test: /^table: rowspan crossing/, code: 'table-rowspan-clipped', class: 'lossy' },
|
|
50
51
|
{ test: /^table: rowspan continuation/, code: 'table-rowspan-origin-missing', class: 'degraded' },
|
|
51
|
-
{ test: /^table: .*padded with empty cells/, code: 'table-groups-normalized', class: '
|
|
52
|
+
{ test: /^table: .*padded with empty cells/, code: 'table-groups-normalized', class: 'degraded' },
|
|
52
53
|
{ test: /^table: a cell holds block content/, code: 'table-groups-normalized', class: 'normalized' },
|
|
54
|
+
{ test: /^table: the merged id/, code: 'table-wrapper-id-dropped', class: 'lossy' },
|
|
55
|
+
{ test: /^table: a blank row has no pipe-table source spelling/, code: 'table-blank-row-spelled', class: 'normalized' },
|
|
53
56
|
{ test: /^table: .*converted with the implicit head\/body split/, code: 'table-row-groups-invalid', class: 'degraded' },
|
|
54
57
|
{ test: /^table: .*preserved in the Carve AST as `rowGroups`/, code: 'table-groups-flattened', class: 'degraded' },
|
|
55
58
|
{ test: /^table: a foot row's row header is dropped/, code: 'table-foot-row-header-dropped', class: 'lossy' },
|
package/dist/reverse.js
CHANGED
|
@@ -983,9 +983,8 @@ function table(ctx, c, captionOverride, shortCaptionOverride = null) {
|
|
|
983
983
|
}));
|
|
984
984
|
// Row-head columns no longer send a table to the list-table form: the pipe
|
|
985
985
|
// table marks those cells directly (see `rowHeadCols` above), which is
|
|
986
|
-
// readable, needs no extension, and round-trips. Block cells
|
|
987
|
-
//
|
|
988
|
-
// no pipe form for a cell holding blocks at all.
|
|
986
|
+
// readable, needs no extension, and round-trips. Block cells need the
|
|
987
|
+
// list-table form, as do blank rows without attributes or alignment.
|
|
989
988
|
//
|
|
990
989
|
// A FOOT USED TO BE A SECOND REASON, AND IT WAS A REAL ONE AT THE TIME.
|
|
991
990
|
// The clause read `|| (ctx.target === 'source' && footRaw.length > 0)`.
|
|
@@ -1005,7 +1004,25 @@ function table(ctx, c, captionOverride, shortCaptionOverride = null) {
|
|
|
1005
1004
|
// (corpus 376, spec f074372), so at the time the pipe form genuinely could
|
|
1006
1005
|
// not carry a foot and the list-table detour was the only way to keep it.
|
|
1007
1006
|
// The limitation is gone, so the detour is too.
|
|
1008
|
-
const
|
|
1007
|
+
const occupied = allRaw.map(() => []);
|
|
1008
|
+
const hasBlankRows = allRaw.some((row, r) => {
|
|
1009
|
+
let col = 0;
|
|
1010
|
+
let blank = nCols > 0;
|
|
1011
|
+
for (const raw of row[1]) {
|
|
1012
|
+
while (occupied[r][col])
|
|
1013
|
+
col++;
|
|
1014
|
+
const [attr, align, height, width, content] = raw;
|
|
1015
|
+
if (width > 1 || height > 1 || content.length || fromAttr(attr) || align.t !== 'AlignDefault')
|
|
1016
|
+
blank = false;
|
|
1017
|
+
for (let down = 1; down < height && r + down < allRaw.length; down++) {
|
|
1018
|
+
for (let across = 0; across < width; across++)
|
|
1019
|
+
occupied[r + down][col + across] = true;
|
|
1020
|
+
}
|
|
1021
|
+
col += width;
|
|
1022
|
+
}
|
|
1023
|
+
return blank && !occupied[r].some(Boolean);
|
|
1024
|
+
});
|
|
1025
|
+
const useListTable = ctx.target === 'source' && (hasBlockCells || hasBlankRows);
|
|
1009
1026
|
const pending = allRaw.map(() => Array(nCols));
|
|
1010
1027
|
const rows = [];
|
|
1011
1028
|
// The pandoc blocks that sat at each grid position, kept alongside the
|
|
@@ -1093,6 +1110,7 @@ function table(ctx, c, captionOverride, shortCaptionOverride = null) {
|
|
|
1093
1110
|
attrs: fromAttr(a),
|
|
1094
1111
|
colAligns,
|
|
1095
1112
|
colWidths: columns.map((column) => column.width),
|
|
1113
|
+
blankRows: !hasBlockCells && hasBlankRows,
|
|
1096
1114
|
});
|
|
1097
1115
|
}
|
|
1098
1116
|
const node = { type: 'table', rows };
|
|
@@ -1299,14 +1317,15 @@ function isInlineShaped(cellBlocks) {
|
|
|
1299
1317
|
*/
|
|
1300
1318
|
function listTable(ctx, input) {
|
|
1301
1319
|
const { rows, cellBlocksAt, headRows, footRows, bodies, caption, attrs, colAligns, colWidths } = input;
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
// line, both in the pipe form.
|
|
1306
|
-
warn(ctx, 'table: a cell holds block content, which a pipe table cannot spell - emitted as a `::: list-table` (structure preserved)');
|
|
1320
|
+
warn(ctx, input.blankRows
|
|
1321
|
+
? 'table: a blank row has no pipe-table source spelling - emitted as a `::: list-table` (structure preserved)'
|
|
1322
|
+
: 'table: a cell holds block content, which a pipe table cannot spell - emitted as a `::: list-table` (structure preserved)');
|
|
1307
1323
|
if (bodies.some((b) => b.attrs)) {
|
|
1308
1324
|
warn(ctx, 'list-table: a body group\'s attributes are dropped - the extension has no body to hang them on');
|
|
1309
1325
|
}
|
|
1326
|
+
if (rows.some((row) => row.attrs)) {
|
|
1327
|
+
warn(ctx, 'list-table: a row\'s attributes are dropped - the extension has no row-attribute spelling');
|
|
1328
|
+
}
|
|
1310
1329
|
if (bodies.slice(1).some((b) => b.headRows === 0)) {
|
|
1311
1330
|
warn(ctx, `list-table: the table's ${bodies.length} body groups merge where a later body has no header row to mark its boundary`);
|
|
1312
1331
|
}
|
|
@@ -1471,27 +1490,6 @@ function figure(ctx, c) {
|
|
|
1471
1490
|
return [node];
|
|
1472
1491
|
}
|
|
1473
1492
|
}
|
|
1474
|
-
if (single?.t === 'Table') {
|
|
1475
|
-
// The wrapper and the Table collapse into ONE Carve node, so their
|
|
1476
|
-
// attrs merge rather than the inner one silently winning: pandoc's
|
|
1477
|
-
// readers put the label on the Figure, not on the Table it wraps, and
|
|
1478
|
-
// dropping it took the id a `</#id>` resolves against with it. The
|
|
1479
|
-
// outer id wins, classes union, key/values merge with the outer taking
|
|
1480
|
-
// precedence.
|
|
1481
|
-
//
|
|
1482
|
-
// A table is the only host that collapses this way, because it is the
|
|
1483
|
-
// only one Carve gives a caption of its own. Every other host keeps
|
|
1484
|
-
// both nodes - the wrapper stays a `figure` and the host's own attrs
|
|
1485
|
-
// ride on the target - which is what the Div arm below sorts out.
|
|
1486
|
-
//
|
|
1487
|
-
// It matters most for a §4c table PANEL, whose id is what resolves as
|
|
1488
|
-
// the group's number plus a letter.
|
|
1489
|
-
const node = table(ctx, single.c, caption, shortCaption);
|
|
1490
|
-
const outer = fromAttr(a);
|
|
1491
|
-
if (outer)
|
|
1492
|
-
node.attrs = mergeCAttrs(node.attrs, outer);
|
|
1493
|
-
return [node];
|
|
1494
|
-
}
|
|
1495
1493
|
// A HOST THAT CARRIES ITS OWN ATTRIBUTES ARRIVES INSIDE A DIV. Pandoc's
|
|
1496
1494
|
// BlockQuote, Para and CodeBlock have no Attr slot, so `block()` in the
|
|
1497
1495
|
// forward direction wraps an attributed one in a Div. A `div` is not a
|
|
@@ -1515,6 +1513,21 @@ function figure(ctx, c) {
|
|
|
1515
1513
|
]);
|
|
1516
1514
|
}
|
|
1517
1515
|
}
|
|
1516
|
+
if (host?.t === 'Table') {
|
|
1517
|
+
// A table caption lives on the table, including when a reader puts
|
|
1518
|
+
// an alignment Div between it and the Figure. Outer attributes win.
|
|
1519
|
+
const node = table(ctx, host.c, caption, shortCaption);
|
|
1520
|
+
const outer = fromAttr(a);
|
|
1521
|
+
for (const attrs of [hostAttrs, outer]) {
|
|
1522
|
+
const current = node.attrs;
|
|
1523
|
+
if (current?.id && attrs?.id && current.id !== attrs.id) {
|
|
1524
|
+
warn(ctx, `table: the merged id "${current.id}" is dropped in favor of "${attrs.id}" - one Carve node has one id`);
|
|
1525
|
+
}
|
|
1526
|
+
if (attrs)
|
|
1527
|
+
node.attrs = mergeCAttrs(current, attrs);
|
|
1528
|
+
}
|
|
1529
|
+
return [node];
|
|
1530
|
+
}
|
|
1518
1531
|
if (host && FIGURE_HOSTS.has(host.t)) {
|
|
1519
1532
|
// A single-host `Figure` is an ordinary `figure` around that host -
|
|
1520
1533
|
// the generic captioned wrapper of PART 9 §4b, which is what the
|
package/package.json
CHANGED