@pptx-studio/validate 0.3.0 → 0.4.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 +17 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +110 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @pptx-studio/validate
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7d2c88e: A table cell's fill, borders and text now come from its style the way PowerPoint draws them.
|
|
8
|
+
`tablePartsAt` names the parts that reach a grid position, and `TABLE_PART_ORDER` is the order they
|
|
9
|
+
compose in. `tableCellFill`, `tableBackground` and `tableEdgeLine` resolve one property each and
|
|
10
|
+
name where it came from: a part, `tblBg`, `a:tcPr`, `a:tblPr` or the default grid. Merged cells and
|
|
11
|
+
the owner of every edge segment follow what PowerPoint drew. `tableTextLayer` gives the style's
|
|
12
|
+
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
|
|
13
|
+
built-in style draws. `TextContext.cell` puts a cell's list style and its style's text layer into
|
|
14
|
+
the text cascade, and `Origin` gains `cell` and `tableStyle`. A position or edge a table does not
|
|
15
|
+
have throws the new `MODEL_TABLE_POSITION`.
|
|
16
|
+
|
|
17
|
+
validate adds `V034`, a warning on a table cell that writes a border PowerPoint draws from the cell
|
|
18
|
+
across the edge instead, where that cell writes none or a different one.
|
|
19
|
+
|
|
3
20
|
## 0.3.0
|
|
4
21
|
|
|
5
22
|
### 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. 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,104 @@ 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
|
+
/** The grid position across segment `k` of a cell's `side`, the cell anchored at (r, c). */
|
|
1491
|
+
function acrossOf(side, r, c, owner, k) {
|
|
1492
|
+
if (side === "lnT") return [r - 1, c + k];
|
|
1493
|
+
if (side === "lnL") return [r + k, c - 1];
|
|
1494
|
+
if (side === "lnB") return [r + owner.rows, c + k];
|
|
1495
|
+
return [r + k, c + owner.cols];
|
|
1496
|
+
}
|
|
1497
|
+
/** Each side a cell writes, the side facing it across the edge, and how a segment of it is named. */
|
|
1498
|
+
const SIDES = [
|
|
1499
|
+
{
|
|
1500
|
+
side: "lnT",
|
|
1501
|
+
facing: "lnB",
|
|
1502
|
+
across: "cols",
|
|
1503
|
+
after: true,
|
|
1504
|
+
where: "above column"
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
side: "lnL",
|
|
1508
|
+
facing: "lnR",
|
|
1509
|
+
across: "rows",
|
|
1510
|
+
after: true,
|
|
1511
|
+
where: "left of row"
|
|
1512
|
+
},
|
|
1513
|
+
{
|
|
1514
|
+
side: "lnB",
|
|
1515
|
+
facing: "lnT",
|
|
1516
|
+
across: "cols",
|
|
1517
|
+
after: false,
|
|
1518
|
+
where: "below column"
|
|
1519
|
+
},
|
|
1520
|
+
{
|
|
1521
|
+
side: "lnR",
|
|
1522
|
+
facing: "lnL",
|
|
1523
|
+
across: "rows",
|
|
1524
|
+
after: false,
|
|
1525
|
+
where: "right of row"
|
|
1526
|
+
}
|
|
1527
|
+
];
|
|
1528
|
+
/**
|
|
1529
|
+
* A border a cell writes that PowerPoint draws from the cell across the edge: the cell before owns a
|
|
1530
|
+
* segment where its anchor is level with it, else the cell after where its is, else the cell before
|
|
1531
|
+
* (C9, ADR 0064). PowerPoint's own writer writes every side alike, so this never fires on its output.
|
|
1532
|
+
*/
|
|
1533
|
+
function v034TableBorders(ctx) {
|
|
1534
|
+
forEachElement(ctx, (part, element) => {
|
|
1535
|
+
if (element.local !== "tbl" || namespaceOf(element) !== NS.a) return;
|
|
1536
|
+
const tblGrid = childElements(element).find((child) => child.local === "tblGrid");
|
|
1537
|
+
const cols = tblGrid === void 0 ? 0 : childElements(tblGrid).filter((c) => c.local === "gridCol").length;
|
|
1538
|
+
const cells = childElements(element).filter((child) => child.local === "tr").map((row) => childElements(row).filter((child) => child.local === "tc"));
|
|
1539
|
+
const grid = owners(cells, cols);
|
|
1540
|
+
for (const [r, line] of cells.entries()) for (const [c, cell] of line.entries()) {
|
|
1541
|
+
const owner = grid[r]?.[c];
|
|
1542
|
+
if (owner?.row !== r || owner.col !== c) continue;
|
|
1543
|
+
for (const s of SIDES) {
|
|
1544
|
+
const written = lineOf(cell, s.side);
|
|
1545
|
+
if (written === void 0) continue;
|
|
1546
|
+
const reach = s.across === "cols" ? owner.cols : owner.rows;
|
|
1547
|
+
for (let k = 0; k < reach; k++) {
|
|
1548
|
+
const [nr, nc] = acrossOf(s.side, r, c, owner, k);
|
|
1549
|
+
const other = grid[nr]?.[nc];
|
|
1550
|
+
if (other === null || other === void 0) continue;
|
|
1551
|
+
const otherLevel = s.across === "cols" ? other.col === nc : other.row === nr;
|
|
1552
|
+
if (!(s.after ? otherLevel || k > 0 : k > 0 && otherLevel)) continue;
|
|
1553
|
+
const drawn = lineOf(cells[other.row][other.col], s.facing);
|
|
1554
|
+
if (drawn !== void 0 && alike(drawn, written)) continue;
|
|
1555
|
+
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 cell (${String(other.row + 1)}, ${String(other.col + 1)}), which writes ${drawn === void 0 ? "none" : "a different one"} - measured in C9.`);
|
|
1556
|
+
break;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1456
1562
|
/**
|
|
1457
1563
|
* Presentation-family parts only.
|
|
1458
1564
|
*
|
|
@@ -2305,7 +2411,8 @@ const IMPLEMENTATIONS = {
|
|
|
2305
2411
|
V030: v030TableGrid,
|
|
2306
2412
|
V031: v031TableAttributes,
|
|
2307
2413
|
V032: v032TableStyleIds,
|
|
2308
|
-
V033: v033TableStyles
|
|
2414
|
+
V033: v033TableStyles,
|
|
2415
|
+
V034: v034TableBorders
|
|
2309
2416
|
};
|
|
2310
2417
|
/** Rules that cannot run without the archive bytes. See `Context.archive`. */
|
|
2311
2418
|
const ARCHIVE_RULES = ["V003"];
|