Skip to content

Commit

Permalink
[FIX] xlsx: fix table total row export
Browse files Browse the repository at this point in the history
In Excel, the total row of a table should only contain either:
- string cells
- formula cells, with an attribute table column to mark them as custom
total formulas.

closes #5042

Task: 4206619
X-original-commit: cc77ce8
Signed-off-by: Rémi Rahir (rar) <[email protected]>
Signed-off-by: Adrien Minne (adrm) <[email protected]>
  • Loading branch information
hokolomopo committed Sep 30, 2024
1 parent 70e8b8f commit eb412ab
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 5 deletions.
9 changes: 9 additions & 0 deletions src/xlsx/functions/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ function addTableColumns(table: ExcelTableData, sheetData: ExcelSheetData): XMLS
["id", i + 1], // id cannot be 0
["name", colName],
];
if (table.config.totalRow) {
// Note: To be 100% complete, we could also add a `totalsRowLabel` attribute for total strings, and a tag
// `<totalsRowFormula>` for the formula of the total. But those doesn't seem to be mandatory for Excel.
const colTotalXc = toXC(tableZone.left + i, tableZone.bottom);
const colTotalContent = sheetData.cells[colTotalXc]?.content;
if (colTotalContent?.startsWith("=")) {
colAttributes.push(["totalsRowFunction", "custom"]);
}
}
columns.push(escapeXml/*xml*/ `<tableColumn ${formatAttributes(colAttributes)}/>`);
}

Expand Down
14 changes: 13 additions & 1 deletion src/xlsx/functions/worksheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,12 @@ export function addRows(
({ attrs: additionalAttrs, node: cellNode } = addContent(label, construct.sharedStrings));
} else if (cell.content && cell.content !== "") {
const isTableHeader = isCellTableHeader(c, r, sheet);
const isTableTotal = isCellTableTotal(c, r, sheet);
const isPlainText = !!(cell.format && isTextFormat(data.formats[cell.format]));
({ attrs: additionalAttrs, node: cellNode } = addContent(
cell.content,
construct.sharedStrings,
isTableHeader || isPlainText
isTableHeader || isTableTotal || isPlainText
));
}
attributes.push(...additionalAttrs);
Expand Down Expand Up @@ -150,6 +151,17 @@ function isCellTableHeader(col: HeaderIndex, row: HeaderIndex, sheet: ExcelSheet
});
}

function isCellTableTotal(col: HeaderIndex, row: HeaderIndex, sheet: ExcelSheetData): boolean {
return sheet.tables.some((table) => {
if (!table.config.totalRow) {
return false;
}
const zone = toZone(table.range);
const totalZone = { ...zone, top: zone.bottom };
return isInside(col, row, totalZone);
});
}

export function addHyperlinks(
construct: XLSXStructure,
data: ExcelWorkbookData,
Expand Down
7 changes: 4 additions & 3 deletions tests/xlsx/__snapshots__/xlsx_export.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -19095,11 +19095,12 @@ exports[`Test XLSX export Export data filters Export data filters snapshot 1`] =

exports[`Test XLSX export Export data filters Table style is correctly exported 1`] = `
{
"content": "<table id="1" name="Table1" displayName="Table1" ref="A1:A4" headerRowCount="1" totalsRowCount="1" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<autoFilter ref="A1:A4">
"content": "<table id="1" name="Table1" displayName="Table1" ref="A1:B4" headerRowCount="1" totalsRowCount="1" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<autoFilter ref="A1:B4">
</autoFilter>
<tableColumns count="1">
<tableColumns count="2">
<tableColumn id="1" name="Column0"/>
<tableColumn id="2" name="Column1" totalsRowFunction="custom"/>
</tableColumns>
<tableStyleInfo name="TableStyleMedium9" showFirstColumn="1" showLastColumn="1" showRowStripes="1" showColumnStripes="1"/>
</table>",
Expand Down
11 changes: 10 additions & 1 deletion tests/xlsx/xlsx_export.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1500,7 +1500,7 @@ describe("Test XLSX export", () => {

test("Table style is correctly exported", async () => {
const model = new Model();
createTable(model, "A1:A4", {
createTable(model, "A1:B4", {
totalRow: true,
firstColumn: true,
lastColumn: true,
Expand All @@ -1509,6 +1509,8 @@ describe("Test XLSX export", () => {
bandedColumns: true,
styleId: "TableStyleMedium9",
});
setCellContent(model, "A4", "5");
setCellContent(model, "B4", "=65+9");
const exported = await exportPrettifiedXlsx(model);
const tableFile = exported.files.find((file) => file.path === "xl/tables/table1.xml");
const xml = parseXML(new XMLString((tableFile as XLSXExportXMLFile)?.content));
Expand All @@ -1524,6 +1526,13 @@ describe("Test XLSX export", () => {
expect(tableStyle?.getAttribute("showRowStripes")).toEqual("1");
expect(tableStyle?.getAttribute("showColumnStripes")).toEqual("1");

const worksheet = exported.files.find((file) => file.path === "xl/worksheets/sheet0.xml");
const sheetXML = parseXML(new XMLString((worksheet as XLSXExportXMLFile)?.content));
const A4 = sheetXML.querySelector("worksheet row c[r='A4']");
expect(A4?.getAttribute("t")).toEqual("s"); // A4 was exported as a string
const tableCol2 = xml.querySelector("tableColumn[id='2']");
expect(tableCol2?.getAttribute("totalsRowFunction")).toEqual("custom"); // Column with B4 has a custom total row function

expect(tableFile).toMatchSnapshot();
});

Expand Down

0 comments on commit eb412ab

Please sign in to comment.