Skip to content

Commit

Permalink
feat(table): use tags to expand rows or (nested) columns
Browse files Browse the repository at this point in the history
  • Loading branch information
singerla committed Aug 7, 2024
1 parent cf7013e commit 861e328
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 113 deletions.
128 changes: 113 additions & 15 deletions __tests__/modify-nested-table.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Automizer, { XmlElement } from '../src/index';
import { vd } from '../src/helper/general-helper';
import Automizer, { modify, TableData } from '../src/index';

test('read table data from slide', async () => {
const automizer = new Automizer({
Expand All @@ -12,22 +11,121 @@ test('read table data from slide', async () => {
.load(`NestedTables.pptx`, 'tables');

await pres
.addSlide('tables', 2, async (slide) => {
const info = await slide.getElement('NestedTable2');
const data = info.getTableData().body;
.addSlide('tables', 3, async (slide) => {
const tableData: TableData = {
body: [
{
values: [
'top left',
'sub-1',
null,
null,
'sub-2',
null,
null,
'Last',
],
styles: [
{
border: [
{
tag: 'lnB',
weight: 35000,
color: {
type: 'srgbClr',
value: 'aacc00',
},
},
],
},
{
border: [
{
tag: 'lnB',
weight: 8500,
type: 'sysDot',
color: {
type: 'srgbClr',
value: 'aacc00',
},
},
{
tag: 'lnR',
weight: 8500,
type: 'sysDot',
color: {
type: 'srgbClr',
value: 'aacc00',
},
},
],
},
],
},
{ values: [undefined, 't1', 't2', 't3', 't3', 't3', 't3', ''] },
{ values: ['label 0', 1, 2, 3, 3, 't3', 't3', 'l0'] },
{ values: ['label 1', 123, 345, 4563, 4671, 't3', 't3', 'l1'] },
{ values: ['label 2', 123, 345, 4562, 4672] },
{ values: ['label 3', 123, 345, 4561, 4673, 't3', 't3', 'l3'] },
{ values: ['', 'Foo', 'ter', 4564, 'foo2', 't3', 't3', ''] },
],
};

slide.modifyElement('NestedTable2', (element: XmlElement) => {
data.forEach((tplCell) => {
vd(tplCell);
// Test
});
// XmlHelper.dump(element);
});
slide.modifyElement(
'NestedTable3',
modify.setTable(tableData, {
expand: [
{
mode: 'row',
tag: '{{each:row}}',
count: 3,
},
{
mode: 'column',
tag: '{{each:subSub2}}',
count: 1,
},
{
mode: 'column',
tag: '{{each:sub}}',
count: 1,
},
],
}),
);
})
.write(`read-table-data.test.pptx`);
.addSlide('tables', 4, async (slide) => {
const tableData: TableData = {
body: [
{ values: ['top left', 123, 345, 'subsub3-1', 'subsub3-2', 'Last'] },
{ values: [undefined, 't1', 't2', 't3', 't3', ''] },
{ values: ['label 0', 1, 2, 3, 3, 'l0'] },
{ values: ['label 1', 123, 345, 4563, 4671, 'l1'] },
{ values: ['label 2', 123, 345, 4562, 4672] },
{ values: ['label 3', 123, 345, 4561, 4673, 'l3'] },
{ values: [undefined, 'Foo', 'ter', 4564, 4674, ''] },
],
};

// We have 12 text values in a 3x3 table:
// console.log(data);
slide.modifyElement(
'NestedTable3',
modify.setTable(tableData, {
expand: [
{
mode: 'row',
tag: '{{each:row}}',
count: 3,
},
{
mode: 'column',
tag: '{{each:subSub3}}',
count: 1,
},
],
}),
);
})
.write(`modify-nested-table.test.pptx`);

// expect(data.length).toBe(12);
});
Binary file modified __tests__/pptx-templates/NestedTables.pptx
Binary file not shown.
90 changes: 31 additions & 59 deletions src/dev.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,46 @@
import Automizer, { XmlElement, XmlHelper } from './index';
import { vd } from './helper/general-helper';
import Automizer, { modify, TableData } from './index';

const run = async () => {
const automizer = new Automizer({
templateDir: `${__dirname}/../__tests__/pptx-templates`,
outputDir: `${__dirname}/../__tests__/pptx-output`,
});

const updateId = (element: XmlElement, tag: string, id: number) => {
element.getElementsByTagName(tag).item(0).setAttribute('val', String(id));
const tableData: TableData = {
body: [
{ values: ['top left', 123, 345, 'subsub3-1', 'subsub3-2', 'Last'] },
{ values: [undefined, 't1', 't2', 't3', 't3', ''] },
{ values: ['label 0', 1, 2, 3, 3, 'l0'] },
{ values: ['label 1', 123, 345, 4563, 4671, 'l1'] },
{ values: ['label 2', 123, 345, 4562, 4672] },
{ values: ['label 3', 123, 345, 4561, 4673, 'l3'] },
{ values: [undefined, 'Foo', 'ter', 4564, 4674, ''] },
],
};

const rows = ['row 1', 'row 2'];
const subs = ['sub 1'];

automizer
.loadRoot(`RootTemplate.pptx`)
.load(`NestedTables.pptx`, 'tables')
.addSlide('tables', 1, async (slide) => {
const info = await slide.getElement('NestedTable2');
const data = info.getTableData().body;

slide.modifyElement('NestedTable2', (element: XmlElement) => {
const table = element.getElementsByTagName('a:tbl').item(0);
const tblGrid = element.getElementsByTagName('a:tblGrid').item(0);

data.forEach((tplCell: any) => {
if (tplCell.text === '{{each:row}}') {
rows.forEach((rowKey, r) => {
const newRow = XmlHelper.appendClone(tplCell.rowXml, table);
updateId(newRow, 'a16:rowId', r);
});
}
});

data.forEach((tplCell: any) => {
if (tplCell.text === '{{each:sub}}') {
subs.forEach((colKey, c) => {
if (tplCell.gridSpan) {
const rows = element.getElementsByTagName('a:tr');
for (let r = 0; r < rows.length; r++) {
const row = rows.item(r);
const columns = row.getElementsByTagName('a:tc');
const maxC = tplCell.column + tplCell.gridSpan;
for (let c = tplCell.column; c < maxC; c++) {
const sourceCell = columns.item(c);
const newCell = XmlHelper.appendClone(sourceCell, row);
}
XmlHelper.moveChild(
row.getElementsByTagName('a:extLst').item(0),
);
}
}
});

subs.forEach((colKey, ci) => {
const maxC = tplCell.column + tplCell.gridSpan;
for (let c = tplCell.column; c < maxC; c++) {
const sourceTblGridCol = tblGrid
.getElementsByTagName('a:gridCol')
.item(c);
const newCol = XmlHelper.appendClone(sourceTblGridCol, tblGrid);
updateId(newCol, 'a16:colId', c * (ci + 1));
}
});
}
});

// XmlHelper.dump(element);
});
.addSlide('tables', 4, (slide) => {
slide.modifyElement(
'NestedTable3',
modify.setTable(tableData, {
adjustHeight: false,
adjustWidth: false,
expand: [
{
mode: 'row',
tag: '{{each:row}}',
count: 3,
},
{
mode: 'column',
tag: '{{each:subSub3}}',
count: 1,
},
],
}),
);
})
.write(`dev.pptx`)
.then((summary) => {
Expand Down
92 changes: 64 additions & 28 deletions src/helper/modify-table-helper.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
import { ModifyTableParams, TableData } from '../types/table-types';
import { ModifyTableParams, TableData, TableInfo } from '../types/table-types';
import { ModifyTable } from '../modify/modify-table';
import { XmlDocument, XmlElement } from '../types/xml-types';
import { ShapeModificationCallback } from '../types/types';

export default class ModifyTableHelper {
static setTable =
(data: TableData, params?: ModifyTableParams) =>
(element: XmlDocument | XmlElement): void => {
(element: XmlElement): void => {
const modTable = new ModifyTable(element, data);

if (params?.expand) {
params?.expand.forEach((expand) => {
const tableInfo = ModifyTableHelper.getTableInfo(element);
const targetCell = tableInfo.find(
(infoCell) => infoCell.textContent === expand.tag,
);
if (targetCell) {
if (expand.mode === 'row') {
modTable.expandRows(expand.count, targetCell.row);
} else {
if (targetCell.gridSpan) {
modTable.expandSpanColumns(
expand.count,
targetCell.column,
targetCell.gridSpan,
);
} else {
modTable.expandColumns(expand.count, targetCell.column);
}
}
}
});
}

modTable.modify();

if (params?.setHeight) {
Expand Down Expand Up @@ -47,33 +71,45 @@ export default class ModifyTableHelper {
};

static readTableData =
(info?: TableData): ShapeModificationCallback =>
(info?: TableInfo[]): ShapeModificationCallback =>
(element: XmlElement): void => {
const body = [];
const rows = element.getElementsByTagName('a:tr');
for (let r = 0; r < rows.length; r++) {
const row = rows.item(r);
const columns = row.getElementsByTagName('a:tc');
for (let c = 0; c < columns.length; c++) {
const cell = columns.item(c);
const gridSpan = cell.getAttribute('gridSpan');
const texts = cell.getElementsByTagName('a:t');
const text: string[] = [];
for (let t = 0; t < texts.length; t++) {
text.push(texts.item(t).textContent);
}
body.push({
row: r,
column: c,
rowXml: row,
columnXml: cell,
text: text.join(' '),
gridSpan: Number(gridSpan),
});
}
}
if (typeof info === 'object') {
info.body = body;
if (Array.isArray(info)) {
info.push(...ModifyTableHelper.getTableInfo(element));
}
};

static getTableInfo = (element: XmlElement) => {
const info = <TableInfo[]>[];
const rows = element.getElementsByTagName('a:tr');
if (!rows) {
console.error("Can't find a table row.");
return info;
}

for (let r = 0; r < rows.length; r++) {
const row = rows.item(r);
const columns = row.getElementsByTagName('a:tc');
for (let c = 0; c < columns.length; c++) {
const cell = columns.item(c);
const gridSpan = cell.getAttribute('gridSpan');
const hMerge = cell.getAttribute('hMerge');
const texts = cell.getElementsByTagName('a:t');
const text: string[] = [];
for (let t = 0; t < texts.length; t++) {
text.push(texts.item(t).textContent);
}
info.push({
row: r,
column: c,
rowXml: row,
columnXml: cell,
text: text,
textContent: text.join(''),
gridSpan: Number(gridSpan),
hMerge: Number(hMerge),
});
}
}
return info;
};
}
4 changes: 2 additions & 2 deletions src/helper/modify-text-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export default class ModifyTextHelper {
};

static content =
(label: number | string) =>
(label: number | string | undefined) =>
(element: XmlElement): void => {
if (element.firstChild) {
if (label !== undefined && element.firstChild) {
element.firstChild.textContent = String(label);
}
};
Expand Down
8 changes: 3 additions & 5 deletions src/helper/xml-slide-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { XmlHelper } from './xml-helper';
import HasShapes from '../classes/has-shapes';
import { FindElementSelector } from '../types/types';
import ModifyTableHelper from './modify-table-helper';
import { TableData } from '../types/table-types';
import { TableData, TableInfo } from '../types/table-types';

export const nsMain =
'http://schemas.openxmlformats.org/presentationml/2006/main';
Expand Down Expand Up @@ -115,10 +115,8 @@ export class XmlSlideHelper {
position: XmlSlideHelper.parseShapeCoordinates(slideElement),
hasTextBody: !!XmlSlideHelper.getTextBody(slideElement),
getText: () => XmlSlideHelper.parseTextFragments(slideElement),
getTableData: () => {
const data: TableData = {
body: [],
};
getTableInfo: () => {
const data = <TableInfo[]>[];
ModifyTableHelper.readTableData(data)(slideElement);
return data;
},
Expand Down
Loading

0 comments on commit 861e328

Please sign in to comment.