From 657bc6d7d0474f0b4ff2a93b31eb27db3f81204b Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Mon, 2 Nov 2026 09:00:24 +0800 Subject: [PATCH] feat: excel pivot macro picture and workbook COM operations --- src/main/capabilities/excel-com.test.ts | 63 ++++++ src/main/capabilities/excel-com.ts | 288 ++++++++++++++++++++++++ src/main/capabilities/index.ts | 1 + 3 files changed, 352 insertions(+) create mode 100644 src/main/capabilities/excel-com.test.ts create mode 100644 src/main/capabilities/excel-com.ts diff --git a/src/main/capabilities/excel-com.test.ts b/src/main/capabilities/excel-com.test.ts new file mode 100644 index 0000000..baaef13 --- /dev/null +++ b/src/main/capabilities/excel-com.test.ts @@ -0,0 +1,63 @@ +import { mkdtemp, readFile, rm } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import ExcelJS from "exceljs"; +import { getHandler } from "../engine/registry"; +import type { CatalogComponent, FlowNode } from "../../shared/types"; +import type { RunContext } from "../engine/types"; +import "./excel-com"; + +const directories: string[] = []; +const context = {} as RunContext; +const component = {} as CatalogComponent; +const node = (id: string): FlowNode => ({ id, componentId: id, position: { x: 0, y: 0 }, values: {}, options: {} }); +async function run(id: string, args: Record) { + const handler = getHandler(id); + expect(handler, id).toBeTruthy(); + return handler!(context, node(id), args, component); +} +afterEach(async () => Promise.all(directories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })))); + +const registered = [ + "Excel_Cs_Group", "Excel_Cs_DeletePicture", "Excel_Cs_RefreshPivotTable", "Excel_Cs_DeletePictureV2", + "Excel_Cs_Encrypt", "Excel_Cs_Move", "Excel_Cs_InsertPicture", "Excel_Cs_Merge", "Excel_InsertImage", + "Excel_Cs_InsertPivotView", "Excel_Cs_Interop", "Excel_Cs_InsertPivotTable", "Excel_Cs_RunMacro", + "Excel_Loop", "Excel_Cs_Export", "Excel_Cs_ExcelAddin", "Excel_Cs_InsertPictureV2", + "Excel_Cs_FilterPivotTable", "Excel_Cs_RunMacroV2", "Excel_Cs_SetSlicer", "Excel_ExecuteMacro", + "Excel_Cs_RefreshAll", "Excel_DeleteImage", "Excel_ExportRangeAsPicture" +]; + +describe("Excel COM capabilities", () => { + it("registers every Excel COM capability", () => { for (const id of registered) expect(getHandler(id), id).toBeTruthy(); }); + + it("groups rows, moves sheets, reads dynamic properties and loops content", async () => { + if (process.platform !== "win32") return; + const directory = await mkdtemp(path.join(os.tmpdir(), "rpa-excel-com-")); directories.push(directory); + const filePath = path.join(directory, "workbook.xlsx"); + const workbook = new ExcelJS.Workbook(); + workbook.addWorksheet("Sheet1").getCell("A1").value = "A1"; + workbook.addWorksheet("Second").getCell("A1").value = "S1"; + await workbook.xlsx.writeFile(filePath); + + await expect(run("Excel_Cs_Group", { workbook: { path: filePath }, sheet: "Sheet1", group: 0, groupType: 0, startRow: 1, endRow: 2, save: true })).resolves.toMatchObject({ grouped: true }); + await expect(run("Excel_Cs_Interop", { workbook: { path: filePath }, obj: 2, sheet: "Sheet1", property: "Name" })).resolves.toMatchObject({ value: "Sheet1" }); + await expect(run("Excel_Cs_Move", { workbook: { path: filePath }, sheet: "Second", mode: "按序号", index: 1, save: true })).resolves.toMatchObject({ moved: true }); + await expect(run("Excel_Cs_RefreshAll", { workbook: { path: filePath }, save: true })).resolves.toMatchObject({ refreshed: true }); + const loop = await run("Excel_Loop", { objExcel: { path: filePath } }) as unknown[][]; + expect(loop.length).toBeGreaterThanOrEqual(2); + expect(loop.flat().join(",")).toContain("A1"); + }, 120000); + + it("exports a workbook to PDF through Excel COM", async () => { + if (process.platform !== "win32") return; + const directory = await mkdtemp(path.join(os.tmpdir(), "rpa-excel-pdf-")); directories.push(directory); + const filePath = path.join(directory, "source.xlsx"); + const workbook = new ExcelJS.Workbook(); + workbook.addWorksheet("Report").getCell("A1").value = "PDF"; + await workbook.xlsx.writeFile(filePath); + const output = await run("Excel_Cs_Export", { workbook: { path: filePath }, option: "全部", path: directory, name: "report.pdf", cover: true }) as string; + expect(output).toBe(path.join(directory, "report.pdf")); + expect((await readFile(output)).subarray(0, 5).toString()).toMatch(/^%PDF-/); + }, 120000); +}); diff --git a/src/main/capabilities/excel-com.ts b/src/main/capabilities/excel-com.ts new file mode 100644 index 0000000..c8bcef2 --- /dev/null +++ b/src/main/capabilities/excel-com.ts @@ -0,0 +1,288 @@ +import fs from "node:fs"; +import path from "node:path"; +import ExcelJS from "exceljs"; +import { register } from "../engine/registry"; +import { RuntimeError, type CapabilityHandler } from "../engine/types"; +import { powershell, psString } from "./desktop"; +import { releaseWorkbook } from "./excel-store"; + +function text(args: Record, keys: string[], fallback = ""): string { + for (const key of keys) { + const value = args[key]; + if (value !== undefined && value !== null && value !== "") return String(value); + } + return fallback; +} + +function bool(value: unknown, fallback = false): boolean { + if (value === undefined || value === null || value === "") return fallback; + return [true, 1, "1", "true", "True", "TRUE", "yes", "是", "开启"].includes(value as never); +} + +function number(value: unknown, fallback = 0): number { const result = Number(value); return Number.isFinite(result) ? result : fallback; } + +function workbookPath(args: Record, nodeId: string, componentId: string, keys = ["workbook", "objExcel", "objExcelWorkBook", "destWorkbook"]): string { + for (const key of keys) { + const raw = args[key]; + const candidate = raw && typeof raw === "object" ? (raw as { path?: unknown }).path : raw; + const value = String(candidate ?? "").trim(); + if (value && value !== "Excel文件" && value !== "objExcelWorkBook") return path.resolve(value); + } + throw new RuntimeError("validation_error", "Excel文件路径为空", { nodeId, componentId }); +} + +function sheetExpression(args: Record): string { + const sheet = text(args, ["sheet", "sheetName"]); + return sheet ? `$workbook.Worksheets.Item(${psString(sheet)})` : "$workbook.ActiveSheet"; +} + +async function runExcelCom(filePath: string, body: string, save = false, readonly = false, timeout = 120000): Promise { + const openMode = readonly ? "$true" : "$false"; + const saveMode = save ? "$true" : "$false"; + const script = [ + "$excel=$null;$workbook=$null;$rpaOutput=$null", + "try {", + " $excel=New-Object -ComObject Excel.Application;$excel.Visible=$false;$excel.DisplayAlerts=$false", + " $workbook=$excel.Workbooks.Open(" + psString(filePath) + ",0," + openMode + ")", + " " + body, + " if (" + saveMode + ") { $workbook.Save() }", + " if ($null -ne $rpaOutput) { [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes(($rpaOutput | ConvertTo-Json -Compress -Depth 6))) | Write-Output }", + "} finally {", + " if($null -ne $workbook){try{$workbook.Close($false)}catch{};try{[void][Runtime.InteropServices.Marshal]::ReleaseComObject($workbook)}catch{}}", + " if($null -ne $excel){try{$excel.Quit()}catch{};try{[void][Runtime.InteropServices.Marshal]::ReleaseComObject($excel)}catch{}}", + " [GC]::Collect();[GC]::WaitForPendingFinalizers()", + "}" + ].join("\n"); + const output = (await powershell(script, timeout)).trim(); + if (save) releaseWorkbook(filePath); + if (!output) return null; + try { return JSON.parse(Buffer.from(output, "base64").toString("utf8")); } + catch { throw new RuntimeError("excel_com_failed", "Excel COM 输出异常", { detail: output.slice(0, 300) }); } +} + +function list(value: unknown): string[] { + if (Array.isArray(value)) return value.map((item) => String(item)).filter(Boolean); + const raw = String(value ?? "").trim(); + if (!raw) return []; + try { const parsed = JSON.parse(raw); return Array.isArray(parsed) ? parsed.map(String) : [String(parsed)]; } + catch { return raw.split(/[,,;;\s]+/).filter(Boolean); } +} +const group: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + const ungroup = String(args.group ?? "0") === "1"; + const columns = String(args.groupType ?? "0") === "1"; + const start = columns ? text(args, ["startColumn"], "A") : String(number(args.startRow, 1)); + const end = columns ? text(args, ["endColumn"], text(args, ["startColumn"], "A")) : String(number(args.endRow, number(args.startRow, 1))); + const method = ungroup ? "Ungroup" : "Group"; + const body = `$sheet=${sheetExpression(args)};$start=${psString(start)};$end=${psString(end)}; + $count=if(${columns ? "$true" : "$false"}){$sheet.UsedRange.Columns.Count}else{$sheet.UsedRange.Rows.Count}; + foreach($name in @("start","end")){ $v=[int](Get-Variable $name -ValueOnly); if($v -lt 0){ Set-Variable $name ($count+$v+1) } }; + $range=$sheet.Range("$($start):$($end)");$range.${method}|Out-Null`; + await runExcelCom(filePath, body, bool(args.save)); + return { grouped: !ungroup, type: columns ? "columns" : "rows" }; +}; + +const deletePicture: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook", "objExcel"]); + const name = text(args, ["name", "pictureName"]); + if (!name) throw new RuntimeError("validation_error", "图片名称为空", { nodeId: node.id, componentId: node.componentId }); + const body = `$sheet=${sheetExpression(args)};$sheet.Shapes.Item(${psString(name)}).Delete()`; + await runExcelCom(filePath, body, bool(args.save)); + return { deleted: name }; +}; + +const insertPicture: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook", "objExcel"]); + const image = path.resolve(text(args, ["path", "pictureName"])); + if (!fs.existsSync(image)) throw new RuntimeError("validation_error", "图片文件不存在", { nodeId: node.id, componentId: node.componentId, detail: image }); + const row = number(args.row, 1); const column = number(args.column, 1); + const left = number(args.Left ?? args.left, 20); const top = number(args.Top ?? args.top, 20); + const width = number(args.Width ?? args.width, -1); const height = number(args.Height ?? args.height, -1); + const scale = width > 0 && height > 0 ? "$true" : "$false"; + const body = `$sheet=${sheetExpression(args)};$cell=$sheet.Cells(${row},${column});$left=${left}+if($left -gt 0){$cell.Left}else{0};$top=${top}+if($top -gt 0){$cell.Top}else{0};[void]$sheet.Shapes.AddPicture(${psString(image)},$false,${scale},$left,$top,${width},${height})`; + await runExcelCom(filePath, body, bool(args.save)); + return { inserted: image }; +}; + +const encryptExcel: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + const password = String(args.password1 ?? args.password ?? ""); + const confirm = String(args.password2 ?? password); + if (!password) throw new RuntimeError("validation_error", "Excel密码为空", { nodeId: node.id, componentId: node.componentId }); + if (password !== confirm) throw new RuntimeError("validation_error", "两次输入的Excel密码不一致", { nodeId: node.id, componentId: node.componentId }); + const temporary = `${filePath}.${Date.now()}.enc.xlsx`; + try { + await runExcelCom(filePath, `$workbook.SaveAs(${psString(temporary)},51,${psString(password)},${psString(confirm)})`, false); + fs.rmSync(temporary, { force: true }); + // COM SaveAs cannot replace the workbook while it is open, so save beside it and rename after Close. + } finally { + if (fs.existsSync(temporary)) { + fs.rmSync(filePath, { force: true }); + fs.renameSync(temporary, filePath); + } + releaseWorkbook(filePath); + } + return filePath; +}; + +const moveSheet: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + const mode = text(args, ["mode"], "按序号"); const after = text(args, ["option"], "之前").includes("之后"); + const targetName = text(args, ["name", "index"]); const position = text(args, ["position", "index"], "第一位"); + const index = number(args.index, 1); + const afterMode = after ? "$true" : "$false"; + const body = `$sheet=${sheetExpression(args)};$count=$workbook.Worksheets.Count;$targetIndex=0; + if(${psString(mode)} -eq "按名称"){ $targetName=${psString(targetName)}; for($i=1;$i -le $count;$i++){ if($workbook.Worksheets.Item($i).Name -eq $targetName){$targetIndex=$i;break} }; if($targetIndex -eq 0){throw "target_sheet_not_found"} } + elseif(${psString(mode)} -eq "按位置"){ $p=${psString(position)}; if($p -eq "第一位"){$targetIndex=1}elseif($p -eq "最后一位"){$targetIndex=$count}else{ $delta=-1; if($p -eq "向后移一位"){$delta=1}; $targetIndex=$sheet.Index+$delta } } + else { $targetIndex=${index}; if($targetIndex -lt 0){$targetIndex=$count+$targetIndex+1} }; + $targetIndex=[Math]::Min([Math]::Max(1,$targetIndex),$count);$target=$workbook.Worksheets.Item($targetIndex); + if(${afterMode}){ if($target.Index -ne $sheet.Index){ $sheet.Move($null,$target)|Out-Null } } + else { if($target.Index -ne $sheet.Index){ $sheet.Move($target,$null)|Out-Null } }`; + await runExcelCom(filePath, body, bool(args.save)); + return { moved: true }; +}; +const mergeSheet: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + const destPath = workbookPath(args, node.id, node.componentId, ["destWorkbook", "workbook"]); + const horizontal = String(args.option ?? "0") === "1"; + const horizontalMode = horizontal ? "$true" : "$false"; + const saveDest = bool(args.save) ? "$true" : "$false"; + const body = `$source=${sheetExpression(args)};$dest=$excel.Workbooks.Open(${psString(destPath)});$target=$dest.Worksheets.Item(${psString(text(args, ["destSheet"], "Sheet1"))}); + if(${horizontalMode}){$offset=$target.UsedRange.Columns.Count+1}else{$offset=$target.UsedRange.Rows.Count+1}; + for($r=1;$r -le $source.UsedRange.Rows.Count;$r++){for($c=1;$c -le $source.UsedRange.Columns.Count;$c++){ if(${horizontalMode}){$target.Cells($r,$offset+$c-1).Value2=$source.Cells($r,$c).Value2}else{$target.Cells($offset+$r-1,$c).Value2=$source.Cells($r,$c).Value2}}}; + if(${saveDest}){$dest.Save()}`; + await runExcelCom(filePath, body, false); + return { merged: true, target: destPath }; +}; + +const refreshAll: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + await runExcelCom(filePath, "$workbook.RefreshAll()", bool(args.save)); + return { refreshed: true }; +}; + +const refreshPivot: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + const name = text(args, ["pivotTable"]); const sheetExpr = sheetExpression(args); + const body = name ? `${sheetExpr}.PivotTables(${psString(name)}).RefreshTable()` : `${sheetExpr}.PivotTables() | ForEach-Object { $_.RefreshTable() }`; + await runExcelCom(filePath, body, bool(args.save)); + return { refreshed: true, name: name || "all" }; +}; + +const filterPivot: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + const field = text(args, ["field"]); const values = list(args.fieldValue); + if (!field) throw new RuntimeError("validation_error", "筛选字段为空", { nodeId: node.id, componentId: node.componentId }); + const encoded = Buffer.from(JSON.stringify(values), "utf16le").toString("base64"); + const body = `$pt=${sheetExpression(args)}.PivotTables(${psString(text(args, ["pivotTable"]))});$pf=$pt.PivotFields(${psString(field)}); + try{$pf.ClearAllFilters()}catch{}; + $wanted=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String("${encoded}"))|ConvertFrom-Json; + foreach($item in $pf.PivotItems()){ $selected=if($wanted.Count -eq 0){$true}else{$wanted -contains $item.Name}; $item.Visible=$selected }`; + await runExcelCom(filePath, body, bool(args.save)); + return { field, values }; +}; + +const createPivot: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + const chart = node.componentId.includes("PivotView"); + let rule: Record = {}; + try { rule = JSON.parse(String(args.rule || "{}")) as Record; } catch { /* sourceData can still be supplied separately. */ } + const source = text({ ...rule, ...args }, ["SourceData", "sourceData", "source"]); + if (!source) throw new RuntimeError("validation_error", "透视表数据源为空", { nodeId: node.id, componentId: node.componentId }); + const name = text(rule, ["Name", "name"], chart ? "RPAPivotChart" : "RPAPivotTable"); + const body = `$sheet=${sheetExpression(args)};$cache=$excel.ActiveWorkbook.PivotCaches().Create(1,$sheet.Range(${psString(source)})); + $dest=$sheet.Cells(1,20);$pt=$cache.CreatePivotTable($dest,${psString(name)}); + if(${chart ? "$true" : "$false"}){[void]$sheet.Shapes.AddChart(-4109,20,20,420,260)}`; + await runExcelCom(filePath, body, bool(args.save)); + return { name, chart }; +}; +const runMacro = (scriptKey: "script" | "code" | "macroName"): CapabilityHandler => async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook", "objExcel"]); + const macro = text(args, [scriptKey, "script", "code", "macroName", "name"]); + if (!macro) throw new RuntimeError("validation_error", "宏名称为空", { nodeId: node.id, componentId: node.componentId }); + const parameters = list(args.args ?? args.macroArgs).map((item) => psString(item)).join(","); + const body = `$result=$excel.Run(${psString(macro)}${parameters ? "," + parameters : ""});$rpaOutput=@{result=$result}`; + return await runExcelCom(filePath, body, bool(args.save), false, 180000); +}; + +const interop: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook", "objExcel"]); + const property = text(args, ["property"]); + if (!property) throw new RuntimeError("validation_error", "属性或方法名为空", { nodeId: node.id, componentId: node.componentId }); + const objectKind = number(args.obj ?? args.object, 0); + const range = text(args, ["range"], "A1"); + const target = objectKind === 0 ? "$excel" : objectKind === 1 ? "$workbook" : objectKind === 2 ? sheetExpression(args) : `${sheetExpression(args)}.Range(${psString(range)})`; + const value = args.value; + const empty = value === undefined || value === null || value === ""; + const body = empty + ? `$target=${target};$rpaOutput=@{value=$target.GetType().InvokeMember(${psString(property)},[Reflection.BindingFlags]::GetProperty,$null,$target,@())}` + : `$target=${target};[void]$target.GetType().InvokeMember(${psString(property)},[Reflection.BindingFlags]::SetProperty,$null,$target,@(${psString(String(value))}));$rpaOutput=@{set=$true}`; + return await runExcelCom(filePath, body, bool(args.save)); +}; + +const exportPdf: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + const folder = text(args, ["path"]); + const name = text(args, ["name"], path.basename(filePath, path.extname(filePath)) + ".pdf"); + const output = path.join(path.resolve(folder), /\.(pdf)$/i.test(name) ? name : `${name}.pdf`); + fs.mkdirSync(path.dirname(output), { recursive: true }); + if (!bool(args.cover, true) && fs.existsSync(output)) throw new RuntimeError("validation_error", "PDF 文件已存在", { detail: output }); + const activateOnly = text(args, ["option"], "全部") === "激活页"; + const body = `if(${activateOnly ? "$true" : "$false"}){$workbook.ActiveSheet.ExportAsFixedFormat(0,${psString(output)})}else{$workbook.ExportAsFixedFormat(0,${psString(output)})}`; + await runExcelCom(filePath, body, false); + return output; +}; + +const exportRangePicture: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + const folder = text(args, ["path"]); const name = text(args, ["name"], `excel-range-${Date.now()}`); + const output = path.join(path.resolve(folder), /\.(png|jpg|jpeg)$/i.test(name) ? name : `${name}.png`); + fs.mkdirSync(path.dirname(output), { recursive: true }); + const body = `$range=${sheetExpression(args)}.Range(${psString(text(args, ["range"], "A1"))});$range.CopyPicture(1,2)|Out-Null;$shape=${sheetExpression(args)}.ChartObjects().Add(0,0,$range.Width,$range.Height);$chart=$shape.Chart;$chart.Paste();[void]$chart.Export(${psString(output)},"PNG");$shape.Delete()`; + await runExcelCom(filePath, body, false); + return output; +}; + +const setSlicer: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["workbook"]); + const name = text(args, ["slicerName"]); const values = list(args.slicerOptions); + if (!name) throw new RuntimeError("validation_error", "切片器名称为空", { nodeId: node.id, componentId: node.componentId }); + const encoded = Buffer.from(JSON.stringify(values), "utf16le").toString("base64"); + const body = `$cache=$workbook.SlicerCaches.Item(${psString(name)}); + $wanted=[Text.Encoding]::Unicode.GetString([Convert]::FromBase64String("${encoded}"))|ConvertFrom-Json; + foreach($item in $cache.SlicerItems()){ $item.Selected=if($wanted.Count -eq 0){$true}else{$wanted -contains $item.Name} }`; + await runExcelCom(filePath, body, bool(args.save)); + return { slicer: name, values }; +}; + +const excelLoop: CapabilityHandler = async (_ctx, node, args) => { + const filePath = workbookPath(args, node.id, node.componentId, ["objExcel", "workbook"]); + const workbook = new ExcelJS.Workbook(); + await workbook.xlsx.readFile(filePath); + const rows: unknown[][] = []; + for (const sheet of workbook.worksheets) { + for (let row = 1; row <= sheet.rowCount; row += 1) { + const cells: unknown[] = []; + for (let column = 1; column <= sheet.columnCount; column += 1) cells.push(sheet.getCell(row, column).value ?? ""); + if (cells.some((cell) => cell !== "")) rows.push(cells); + } + } + return rows; +}; + +register("Excel_Cs_Group", group); +register("Excel_Cs_Encrypt", encryptExcel); +register(["Excel_Cs_DeletePicture", "Excel_Cs_DeletePictureV2", "Excel_DeleteImage"], deletePicture); +register(["Excel_Cs_InsertPicture", "Excel_Cs_InsertPictureV2", "Excel_InsertImage"], insertPicture); +register("Excel_Cs_Move", moveSheet); +register("Excel_Cs_Merge", mergeSheet); +register(["Excel_Cs_RefreshAll", "Excel_Cs_RefreshPivotTable"], async (ctx, node, args, component) => node.componentId === "Excel_Cs_RefreshPivotTable" ? refreshPivot(ctx, node, args, component) : refreshAll(ctx, node, args, component)); +register("Excel_Cs_FilterPivotTable", filterPivot); +register(["Excel_Cs_InsertPivotTable", "Excel_Cs_InsertPivotView"], createPivot); +register(["Excel_Cs_RunMacro", "Excel_Cs_RunMacroV2", "Excel_ExecuteMacro"], runMacro("script")); +register("Excel_Cs_Interop", interop); +register("Excel_Cs_Export", exportPdf); +register("Excel_Cs_ExcelAddin", runMacro("code")); +register("Excel_Cs_SetSlicer", setSlicer); +register("Excel_Loop", excelLoop); +register("Excel_ExportRangeAsPicture", exportRangePicture); \ No newline at end of file diff --git a/src/main/capabilities/index.ts b/src/main/capabilities/index.ts index 3dba079..699f8bf 100644 --- a/src/main/capabilities/index.ts +++ b/src/main/capabilities/index.ts @@ -29,6 +29,7 @@ import "./system-extended"; import "./office-file-extended"; import "./office-aliases"; import "./excel-common"; +import "./excel-com"; import "./window-extended"; import "./data-extended"; import "./utility-extended";