diff --git a/packages/ooxml.js/src/typed/pptx/chart.test.ts b/packages/ooxml.js/src/typed/pptx/chart.test.ts index 8b3edda51..715c71e5b 100644 --- a/packages/ooxml.js/src/typed/pptx/chart.test.ts +++ b/packages/ooxml.js/src/typed/pptx/chart.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import type { XmlElement } from "../../model/node"; -import { readChartResidue } from "./chart"; +import { el, txt } from "../../xml/fragment"; +import { readChartResidue, readChartTable } from "./chart"; function chartRoot(): XmlElement { return { @@ -29,3 +30,58 @@ describe("readChartResidue", () => { expect(second.xml).toBe(first.xml); }); }); + +// One bar chart with a single series, its category labels and values in the caches PowerPoint writes +// beside the data reference. +function barChartRoot(): XmlElement { + const cachedPoint = (idx: string, value: string) => + el("c:pt", { idx }, [el("c:v", {}, [txt(value)])]); + return el("c:chartSpace", {}, [ + el("c:chart", {}, [ + el("c:plotArea", {}, [ + el("c:barChart", {}, [ + el("c:ser", {}, [ + el("c:tx", {}, [ + el("c:strRef", {}, [ + el("c:strCache", {}, [cachedPoint("0", "FY26")]), + ]), + ]), + el("c:cat", {}, [ + el("c:strRef", {}, [ + el("c:strCache", {}, [ + cachedPoint("0", "EMEA"), + cachedPoint("1", "APAC"), + ]), + ]), + ]), + el("c:val", {}, [ + el("c:numRef", {}, [ + el("c:numCache", {}, [ + cachedPoint("0", "42"), + cachedPoint("1", "51"), + ]), + ]), + ]), + ]), + ]), + ]), + ]), + ]); +} + +describe("readChartTable", () => { + it('marks the table it produces as origin "chart"', () => { + // A ContentTable is a native table, a chart's cached data, or a spreadsheet range, and a consumer + // holding one cannot otherwise tell which. It matters: a chart's cached numbers are exact and + // quotable, where a vision reading of the same chart would be approximate -- so the two have to be + // distinguishable by something other than a consumer's guess. + const table = readChartTable(barChartRoot(), { + xPt: 0, + yPt: 0, + widthPt: 400, + heightPt: 300, + }); + + expect(table?.origin).toBe("chart"); + }); +}); diff --git a/packages/ooxml.js/src/typed/pptx/chart.ts b/packages/ooxml.js/src/typed/pptx/chart.ts index 0c086bdad..63e314dca 100644 --- a/packages/ooxml.js/src/typed/pptx/chart.ts +++ b/packages/ooxml.js/src/typed/pptx/chart.ts @@ -130,6 +130,7 @@ export function readChartTable( const columnWidthPt = frame.widthPt / (series.length + 1); return { kind: "table", + origin: "chart", rows, columnWidthsPt: Array.from( { length: series.length + 1 }, diff --git a/packages/ooxml.js/src/typed/pptx/diagram.test.ts b/packages/ooxml.js/src/typed/pptx/diagram.test.ts index 915d6c990..2cc18eb2f 100644 --- a/packages/ooxml.js/src/typed/pptx/diagram.test.ts +++ b/packages/ooxml.js/src/typed/pptx/diagram.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import type { XmlElement } from "../../model/node"; -import { readDiagramResidue } from "./diagram"; +import { el, txt } from "../../xml/fragment"; +import { readDiagramResidue, readDiagramText } from "./diagram"; function part(tag: string): XmlElement { return { type: "element", tag, attributes: [], children: [] }; @@ -31,3 +32,36 @@ describe("readDiagramResidue", () => { expect(readDiagramResidue(undefined, undefined, undefined)).toBeUndefined(); }); }); + +// A two-node data model: a doc root, two content nodes, and the parOf connections making it a tree. +function dataModelRoot(): XmlElement { + const point = (id: string, text: string, type?: string) => + el("dgm:pt", type === undefined ? { modelId: id } : { modelId: id, type }, [ + el("dgm:t", {}, [ + el("a:p", {}, [el("a:r", {}, [el("a:t", {}, [txt(text)])])]), + ]), + ]); + const cxn = (srcId: string, destId: string, srcOrd: string) => + el("dgm:cxn", { srcId, destId, type: "parOf", srcOrd }); + return el("dgm:dataModel", {}, [ + el("dgm:ptLst", {}, [ + point("root", "", "doc"), + point("a", "Ad hoc"), + point("b", "Repeatable"), + ]), + el("dgm:cxnLst", {}, [cxn("root", "a", "0"), cxn("root", "b", "1")]), + ]); +} + +describe("readDiagramText", () => { + it('marks every node paragraph as origin "diagram"', () => { + // SmartArt node text reaches the model as ordinary paragraphs, so nothing otherwise distinguishes a + // process flow's step labels from body prose -- and they are not the same thing: the relationships + // between the nodes (the arrows, the hierarchy) are not recovered, which a consumer reading them as + // prose needs to know. + const paragraphs = readDiagramText(dataModelRoot()); + + expect(paragraphs.length).toBeGreaterThan(0); + expect(paragraphs.every((p) => p.origin === "diagram")).toBe(true); + }); +}); diff --git a/packages/ooxml.js/src/typed/pptx/diagram.ts b/packages/ooxml.js/src/typed/pptx/diagram.ts index df69e1391..8b550da99 100644 --- a/packages/ooxml.js/src/typed/pptx/diagram.ts +++ b/packages/ooxml.js/src/typed/pptx/diagram.ts @@ -29,7 +29,7 @@ function diagramTextParagraphs( runs.push({ text: "\n" }); } } - return { kind: "paragraph", runs }; + return { kind: "paragraph", origin: "diagram", runs }; }); }