@pptx-studio/validate 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +135 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @pptx-studio/validate
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- db00af0: `tableBackground` now returns the table background's two layers, `{ fill, effects }`, each taken
|
|
8
|
+
from `a:tblPr` where it writes one and from the style's `tblBg` otherwise. An empty `a:effectLst` in
|
|
9
|
+
`a:tblPr` removes the style's effect, as PowerPoint draws it, and `themedEffects` follows an
|
|
10
|
+
`effectRef` into the theme. `tableCellDiagonals` gives a cell's diagonals as drawn: the anchor's
|
|
11
|
+
own `a:lnTlToBr` and `a:lnBlToTr` across its whole span, a covered position's never, and swapped in a
|
|
12
|
+
right-to-left table.
|
|
13
|
+
|
|
14
|
+
validate's `V034` also warns on every line a covered cell writes, which PowerPoint keeps on save
|
|
15
|
+
and never draws, and on a border written under a position no `a:tc` reached.
|
|
16
|
+
|
|
17
|
+
## 0.4.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- 7d2c88e: A table cell's fill, borders and text now come from its style the way PowerPoint draws them.
|
|
22
|
+
`tablePartsAt` names the parts that reach a grid position, and `TABLE_PART_ORDER` is the order they
|
|
23
|
+
compose in. `tableCellFill`, `tableBackground` and `tableEdgeLine` resolve one property each and
|
|
24
|
+
name where it came from: a part, `tblBg`, `a:tcPr`, `a:tblPr` or the default grid. Merged cells and
|
|
25
|
+
the owner of every edge segment follow what PowerPoint drew. `tableTextLayer` gives the style's
|
|
26
|
+
bold, italic, faces and colour as run properties for the text cascade. `DEFAULT_GRID_LINE` is the 1-pt black line a table with no
|
|
27
|
+
built-in style draws. `TextContext.cell` puts a cell's list style and its style's text layer into
|
|
28
|
+
the text cascade, and `Origin` gains `cell` and `tableStyle`. A position or edge a table does not
|
|
29
|
+
have throws the new `MODEL_TABLE_POSITION`.
|
|
30
|
+
|
|
31
|
+
validate adds `V034`, a warning on a table cell that writes a border PowerPoint draws from the cell
|
|
32
|
+
across the edge instead, where that cell writes none or a different one.
|
|
33
|
+
|
|
3
34
|
## 0.3.0
|
|
4
35
|
|
|
5
36
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -306,6 +306,13 @@ export declare const RULES: readonly [{
|
|
|
306
306
|
readonly evidence: "both";
|
|
307
307
|
readonly title: "a table style id is a braced GUID, and the table-style part is one PowerPoint keeps";
|
|
308
308
|
readonly why: string;
|
|
309
|
+
}, {
|
|
310
|
+
readonly id: "V034";
|
|
311
|
+
readonly category: "required";
|
|
312
|
+
readonly severity: "warning";
|
|
313
|
+
readonly evidence: "measured";
|
|
314
|
+
readonly title: "a border a cell writes is the one PowerPoint draws on that edge";
|
|
315
|
+
readonly why: string;
|
|
309
316
|
}];
|
|
310
317
|
type RuleId = (typeof RULES)[number]['id'];
|
|
311
318
|
export declare function ruleById(id: string): Rule | undefined;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/errors.ts","../src/rules/rules.ts","../src/report/location.ts","../src/report/report.ts","../src/context.ts","../src/validate.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;qBAwBa;KAuBD,4BAA4B;UAEvB;;WAEN;;WAEA;;;;;;;;WAQA;;qBAGE,sBAAsB;WACxB,MAAM;WACN,QAAQ;EAEL,YAAA,MAAM,mBAAmB,iBAAiB,SAAQ;;wBAQhD,gBAAgB,iBAAiB,SAAS;;;;;;;;KCtE9C;KAGA;;KAGA;UAEK;WACN,IAAI;WACJ,UAAU;WACV,UAAU;WACV,UAAU;;WAEV;;WAEA;;;;;WAKA;;qBAgBE
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/errors.ts","../src/rules/rules.ts","../src/report/location.ts","../src/report/report.ts","../src/context.ts","../src/validate.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -311,6 +311,14 @@ const RULES = [
|
|
|
311
311
|
evidence: "both",
|
|
312
312
|
title: "a table style id is a braced GUID, and the table-style part is one PowerPoint keeps",
|
|
313
313
|
why: "`ST_Guid` is a GUID in braces, `CT_TableStyleList/@def` and `CT_TableStyle/@styleId` are required, `b` and `i` are `ST_OnOffStyleType` and a border or fill must hold one. C8 measured each as a repair prompt: an id without braces or with padding, a `tblPr` naming its style twice, a part whose root is not `a:tblStyleLst`, a list without `@def`, a style without `@styleId`, `b=\"yes\"`, an edge with neither `a:ln` nor `a:lnRef`, and an empty `a:fill`. PowerPoint writes the all-zero GUID for the id and empties the part."
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: "V034",
|
|
317
|
+
category: "required",
|
|
318
|
+
severity: "warning",
|
|
319
|
+
evidence: "measured",
|
|
320
|
+
title: "a border a cell writes is the one PowerPoint draws on that edge",
|
|
321
|
+
why: "A grid edge belongs to two cells, and each may write a line for it. C9 drew direct borders, on plain and merged cells, under seven built-ins and the default grid: PowerPoint draws an edge from the cell above or to the left - its `a:lnB` or `a:lnR`, or the style where it writes none - where that cell, merged or not, has its anchor level with the edge. Otherwise the cell below or to the right draws its own `a:lnT` or `a:lnL` where its anchor is level with the edge, and elsewhere the cell above or to the left keeps it. A position no `a:tc` reached owns its edges as a cell writing nothing does, and a covered cell’s own lines are never drawn, though PowerPoint keeps them on save. When PowerPoint sets a border itself it writes every side alike. The deck opens without a word, which is why this is a warning: the file says one border and PowerPoint draws another."
|
|
314
322
|
}
|
|
315
323
|
];
|
|
316
324
|
const BY_ID = new Map(RULES.map((rule) => [rule.id, rule]));
|
|
@@ -1178,8 +1186,8 @@ function forEachElement$1(ctx, visit) {
|
|
|
1178
1186
|
//#endregion
|
|
1179
1187
|
//#region src/rules/required.ts
|
|
1180
1188
|
/**
|
|
1181
|
-
* `V013` … `V017`, `V030`, `V031` and `
|
|
1182
|
-
*
|
|
1189
|
+
* `V013` … `V017`, `V030`, `V031`, `V033` and `V034`: children and attributes that are not optional,
|
|
1190
|
+
* and tables PowerPoint reads otherwise than they say.
|
|
1183
1191
|
*/
|
|
1184
1192
|
/** The twelve attributes of `CT_ColorMapping`. All required, no defaults. */
|
|
1185
1193
|
const CLR_MAP_ATTRIBUTES = [
|
|
@@ -1453,6 +1461,129 @@ function v030TableGrid(ctx) {
|
|
|
1453
1461
|
});
|
|
1454
1462
|
});
|
|
1455
1463
|
}
|
|
1464
|
+
/** Two elements alike but for their own names: the same attributes and the same children. */
|
|
1465
|
+
function sameMarkup(a, b) {
|
|
1466
|
+
const attrs = (e) => [...e.attributes].map((x) => `${x.qname}=${x.value}`).sort().join(" ");
|
|
1467
|
+
const ka = childElements(a);
|
|
1468
|
+
const kb = childElements(b);
|
|
1469
|
+
return attrs(a) === attrs(b) && textContent(a).trim() === textContent(b).trim() && ka.length === kb.length && ka.every((child, i) => child.qname === kb[i]?.qname && sameMarkup(child, kb[i]));
|
|
1470
|
+
}
|
|
1471
|
+
/** Whether a line draws nothing: an `a:noFill`, or no fill at all (C9). */
|
|
1472
|
+
function drawsNothing(line) {
|
|
1473
|
+
const fills = childElements(line).filter((c) => [
|
|
1474
|
+
"noFill",
|
|
1475
|
+
"solidFill",
|
|
1476
|
+
"gradFill",
|
|
1477
|
+
"pattFill"
|
|
1478
|
+
].includes(c.local));
|
|
1479
|
+
return fills.length === 0 || fills.some((c) => c.local === "noFill");
|
|
1480
|
+
}
|
|
1481
|
+
/** Two lines that draw alike: both draw nothing, or they are the same markup. */
|
|
1482
|
+
function alike(a, b) {
|
|
1483
|
+
return drawsNothing(a) && drawsNothing(b) || sameMarkup(a, b);
|
|
1484
|
+
}
|
|
1485
|
+
/** A cell's own `a:tcPr` line, by its local name. */
|
|
1486
|
+
function lineOf(cell, local) {
|
|
1487
|
+
const tcPr = childElements(cell).find((child) => child.local === "tcPr");
|
|
1488
|
+
return tcPr === void 0 ? void 0 : childElements(tcPr).find((child) => child.local === local);
|
|
1489
|
+
}
|
|
1490
|
+
/** Each line a covered cell's own `a:tcPr` writes that would draw something: none is drawn (C9). */
|
|
1491
|
+
function reportCovered(ctx, part, cell, [r, c], owner) {
|
|
1492
|
+
const tcPr = childElements(cell).find((child) => child.local === "tcPr");
|
|
1493
|
+
const lines = tcPr === void 0 ? [] : childElements(tcPr).filter((child) => LINE_TAGS.has(child.local) && !drawsNothing(child));
|
|
1494
|
+
for (const written of lines) ctx.add("V034", elementLocation(part, written), `cell (${String(r + 1)}, ${String(c + 1)}) writes <a:${written.local}>, but it is covered by the span of cell (${String(owner.row + 1)}, ${String(owner.col + 1)}), and PowerPoint never draws a covered cell’s own lines - measured in C9.`);
|
|
1495
|
+
}
|
|
1496
|
+
const LINE_TAGS = /* @__PURE__ */ new Set([
|
|
1497
|
+
"lnL",
|
|
1498
|
+
"lnR",
|
|
1499
|
+
"lnT",
|
|
1500
|
+
"lnB",
|
|
1501
|
+
"lnTlToBr",
|
|
1502
|
+
"lnBlToTr"
|
|
1503
|
+
]);
|
|
1504
|
+
/** The grid position across segment `k` of a cell's `side`, the cell anchored at (r, c). */
|
|
1505
|
+
function acrossOf(side, r, c, owner, k) {
|
|
1506
|
+
if (side === "lnT") return [r - 1, c + k];
|
|
1507
|
+
if (side === "lnL") return [r + k, c - 1];
|
|
1508
|
+
if (side === "lnB") return [r + owner.rows, c + k];
|
|
1509
|
+
return [r + k, c + owner.cols];
|
|
1510
|
+
}
|
|
1511
|
+
/** Each side a cell writes, the side facing it across the edge, and how a segment of it is named. */
|
|
1512
|
+
const SIDES = [
|
|
1513
|
+
{
|
|
1514
|
+
side: "lnT",
|
|
1515
|
+
facing: "lnB",
|
|
1516
|
+
across: "cols",
|
|
1517
|
+
after: true,
|
|
1518
|
+
where: "above column"
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
side: "lnL",
|
|
1522
|
+
facing: "lnR",
|
|
1523
|
+
across: "rows",
|
|
1524
|
+
after: true,
|
|
1525
|
+
where: "left of row"
|
|
1526
|
+
},
|
|
1527
|
+
{
|
|
1528
|
+
side: "lnB",
|
|
1529
|
+
facing: "lnT",
|
|
1530
|
+
across: "cols",
|
|
1531
|
+
after: false,
|
|
1532
|
+
where: "below column"
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
side: "lnR",
|
|
1536
|
+
facing: "lnL",
|
|
1537
|
+
across: "rows",
|
|
1538
|
+
after: false,
|
|
1539
|
+
where: "right of row"
|
|
1540
|
+
}
|
|
1541
|
+
];
|
|
1542
|
+
/**
|
|
1543
|
+
* A border a cell writes that PowerPoint draws from the cell or empty position across the edge, and
|
|
1544
|
+
* any line a covered cell writes (C9, ADR 0064). PowerPoint's own writer writes every side alike and
|
|
1545
|
+
* leaves a covered cell's `a:tcPr` empty, so this never fires on its output.
|
|
1546
|
+
*/
|
|
1547
|
+
function v034TableBorders(ctx) {
|
|
1548
|
+
forEachElement(ctx, (part, element) => {
|
|
1549
|
+
if (element.local !== "tbl" || namespaceOf(element) !== NS.a) return;
|
|
1550
|
+
const tblGrid = childElements(element).find((child) => child.local === "tblGrid");
|
|
1551
|
+
const cols = tblGrid === void 0 ? 0 : childElements(tblGrid).filter((c) => c.local === "gridCol").length;
|
|
1552
|
+
const cells = childElements(element).filter((child) => child.local === "tr").map((row) => childElements(row).filter((child) => child.local === "tc"));
|
|
1553
|
+
const grid = owners(cells, cols);
|
|
1554
|
+
for (const [r, line] of cells.entries()) for (const [c, cell] of line.entries()) {
|
|
1555
|
+
const owner = grid[r]?.[c];
|
|
1556
|
+
if (owner === null || owner === void 0) continue;
|
|
1557
|
+
if (owner.row !== r || owner.col !== c) {
|
|
1558
|
+
reportCovered(ctx, part, cell, [r, c], owner);
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
for (const s of SIDES) {
|
|
1562
|
+
const written = lineOf(cell, s.side);
|
|
1563
|
+
if (written === void 0) continue;
|
|
1564
|
+
const reach = s.across === "cols" ? owner.cols : owner.rows;
|
|
1565
|
+
for (let k = 0; k < reach; k++) {
|
|
1566
|
+
const [nr, nc] = acrossOf(s.side, r, c, owner, k);
|
|
1567
|
+
const other = grid[nr]?.[nc];
|
|
1568
|
+
if (other === void 0) continue;
|
|
1569
|
+
const at = other ?? {
|
|
1570
|
+
row: nr,
|
|
1571
|
+
col: nc,
|
|
1572
|
+
rows: 1,
|
|
1573
|
+
cols: 1
|
|
1574
|
+
};
|
|
1575
|
+
const otherLevel = s.across === "cols" ? at.col === nc : at.row === nr;
|
|
1576
|
+
if (!(s.after ? otherLevel || k > 0 : k > 0 && otherLevel)) continue;
|
|
1577
|
+
const drawn = other === null ? void 0 : lineOf(cells[at.row][at.col], s.facing);
|
|
1578
|
+
if (drawn !== void 0 && alike(drawn, written)) continue;
|
|
1579
|
+
const whose = other === null ? `position (${String(nr + 1)}, ${String(nc + 1)}), which no <a:tc> reached` : `cell (${String(at.row + 1)}, ${String(at.col + 1)}), which writes ` + (drawn === void 0 ? "none" : "a different one");
|
|
1580
|
+
ctx.add("V034", elementLocation(part, written), `cell (${String(r + 1)}, ${String(c + 1)}) writes <a:${s.side}>, but ${s.where} ${String((s.across === "cols" ? nc : nr) + 1)} PowerPoint draws the <a:${s.facing}> of ${whose} - measured in C9.`);
|
|
1581
|
+
break;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1456
1587
|
/**
|
|
1457
1588
|
* Presentation-family parts only.
|
|
1458
1589
|
*
|
|
@@ -2305,7 +2436,8 @@ const IMPLEMENTATIONS = {
|
|
|
2305
2436
|
V030: v030TableGrid,
|
|
2306
2437
|
V031: v031TableAttributes,
|
|
2307
2438
|
V032: v032TableStyleIds,
|
|
2308
|
-
V033: v033TableStyles
|
|
2439
|
+
V033: v033TableStyles,
|
|
2440
|
+
V034: v034TableBorders
|
|
2309
2441
|
};
|
|
2310
2442
|
/** Rules that cannot run without the archive bytes. See `Context.archive`. */
|
|
2311
2443
|
const ARCHIVE_RULES = ["V003"];
|