diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 2e2a0ba..16e3c4a 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -25,6 +25,7 @@ export const formulaSectionNotFoundErr = "Formula section wasn't found in templa export const templateWithInitialDataErr = "Cannot receive template file with initial data"; export const queryTableNotFoundErr = "Query table wasn't found in template"; export const tableNotFoundErr = "Table wasn't found in template"; +export const workbookNotFoundErr = "Workbook wasn't found in template"; export const invalidValueInColumnErr = "Invalid cell value in column"; export const headerNotFoundErr = "Invalid JSON file, header is missing"; export const invalidDataTypeErr = "Invalid JSON file, invalid data type"; @@ -81,6 +82,9 @@ export const element = { dimension: "dimension", selection: "selection", kindCell: "c", + connection: "connection", + worksheet: "worksheet", + workbookView: "workbookView" }; export const elementAttributes = { @@ -113,6 +117,9 @@ export const elementAttributes = { spans: "spans", x14acDyDescent: "x14ac:dyDescent", xr3uid: "xr3:uid", + xr2uid: "xr2:uid", + xr16uid: "xr16:uid", + xruid: "xr:uid", }; export const dataTypeKind = { diff --git a/src/utils/xmlInnerPartsUtils.ts b/src/utils/xmlInnerPartsUtils.ts index c39b968..15d7084 100644 --- a/src/utils/xmlInnerPartsUtils.ts +++ b/src/utils/xmlInnerPartsUtils.ts @@ -21,6 +21,7 @@ import { emptyValue, } from "./constants"; import documentUtils from "./documentUtils"; +import { v4 } from "uuid"; const updateDocProps = async (zip: JSZip, docProps: DocProps = {}): Promise => { const { doc, properties } = await documentUtils.getDocPropsProperties(zip); @@ -131,7 +132,8 @@ const updateWorksheet = (sheetsXmlString: string, sharedStringIndex: string): st const parser: DOMParser = new DOMParser(); const serializer: XMLSerializer = new XMLSerializer(); const sheetsDoc: Document = parser.parseFromString(sheetsXmlString, xmlTextResultType); - sheetsDoc.getElementsByTagName(element.cellValue)[0].innerHTML = sharedStringIndex.toString(); + const cellValue: Element = sheetsDoc.getElementsByTagName(element.cellValue)[0]; + cellValue.innerHTML = sharedStringIndex; const newSheet: string = serializer.serializeToString(sheetsDoc); return newSheet; @@ -235,6 +237,66 @@ const updatePivotTable = (tableXmlString: string, connectionId: string, refreshO return { isPivotTableUpdated, newPivotTable }; }; +const randomizeConnectionsUUID = (connectionsXmlString: string): string => { + const parser: DOMParser = new DOMParser(); + const serializer: XMLSerializer = new XMLSerializer(); + const connectionsDoc: Document = parser.parseFromString(connectionsXmlString, xmlTextResultType); + const connection: Element = connectionsDoc.getElementsByTagName(element.connection)[0]; + connection.setAttribute(elementAttributes.xr16uid, "{" + v4().toUpperCase() + "}"); + const newConnections: string = serializer.serializeToString(connectionsDoc); + + return newConnections; +}; + +const randomizeWorksheetUUID = (worksheetXmlString: string): string => { + const parser: DOMParser = new DOMParser(); + const serializer: XMLSerializer = new XMLSerializer(); + const worksheetDoc: Document = parser.parseFromString(worksheetXmlString, xmlTextResultType); + const worksheet: Element = worksheetDoc.getElementsByTagName(element.worksheet)[0]; + worksheet.setAttribute(elementAttributes.xruid, "{" + v4().toUpperCase() + "}"); + const newSheet: string = serializer.serializeToString(worksheetDoc); + + return newSheet; +}; + +const randomizeTableUUID = (tableXmlString: string): string => { + const parser: DOMParser = new DOMParser(); + const serializer: XMLSerializer = new XMLSerializer(); + const tableDoc: Document = parser.parseFromString(tableXmlString, xmlTextResultType); + const table: Element = tableDoc.getElementsByTagName(element.table)[0]; + const tableUUID: string = "{" + v4().toUpperCase() + "}"; + table.setAttribute(elementAttributes.xruid, tableUUID); + const autoFilter: Element = tableDoc.getElementsByTagName(element.autoFilter)[0]; + autoFilter.setAttribute(elementAttributes.xruid, tableUUID); + const tableColumn: Element = tableDoc.getElementsByTagName(element.tableColumn)[0]; + tableColumn.setAttribute(elementAttributes.xr3uid, "{" + v4().toUpperCase() + "}"); + const newTable: string = serializer.serializeToString(tableDoc); + + return newTable; +}; + +const randomizeQueryTableUUID = (queryTableXmlString: string): string => { + const parser: DOMParser = new DOMParser(); + const serializer: XMLSerializer = new XMLSerializer(); + const queryTableDoc: Document = parser.parseFromString(queryTableXmlString, xmlTextResultType); + const queryTable: Element = queryTableDoc.getElementsByTagName(element.queryTable)[0]; + queryTable.setAttribute(elementAttributes.xr16uid, "{" + v4().toUpperCase() + "}"); + const newQueryTable: string = serializer.serializeToString(queryTableDoc); + + return newQueryTable; +}; + +const randomizeWorkbookUUID = (workbookXmlString: string): string => { + const parser: DOMParser = new DOMParser(); + const serializer: XMLSerializer = new XMLSerializer(); + const workbookDoc: Document = parser.parseFromString(workbookXmlString, xmlTextResultType); + const workbookView: Element = workbookDoc.getElementsByTagName(element.workbookView)[0]; + workbookView.setAttribute(elementAttributes.xr2uid, "{" + v4().toUpperCase() + "}"); + const newWorkbook: string = serializer.serializeToString(workbookDoc); + + return newWorkbook; +}; + export default { updateDocProps, updateConnections, @@ -243,4 +305,9 @@ export default { updatePivotTablesandQueryTables, updateQueryTable, updatePivotTable, + randomizeConnectionsUUID, + randomizeWorksheetUUID, + randomizeTableUUID, + randomizeQueryTableUUID, + randomizeWorkbookUUID }; diff --git a/src/utils/xmlPartsUtils.ts b/src/utils/xmlPartsUtils.ts index c9232b8..97e3e4a 100644 --- a/src/utils/xmlPartsUtils.ts +++ b/src/utils/xmlPartsUtils.ts @@ -11,6 +11,12 @@ import { sharedStringsNotFoundErr, sheetsXmlPath, sheetsNotFoundErr, + tableXmlPath, + queryTableXmlPath, + queryTableNotFoundErr, + workbookXmlPath, + workbookNotFoundErr, + tableNotFoundErr, } from "./constants"; import { replaceSingleQuery } from "./mashupDocumentParser"; import { DocProps, TableData } from "../types"; @@ -66,8 +72,53 @@ const updateWorkbookSingleQueryAttributes = async (zip: JSZip, queryName: string await xmlInnerPartsUtils.updatePivotTablesandQueryTables(zip, queryName, refreshOnOpen, connectionId!); }; +const updateWorkbookGeneratedUUIDs = async (zip: JSZip, updateQueryTable: boolean = false): Promise => { + const sheetsXmlString: string | undefined = await zip.file(sheetsXmlPath)?.async(textResultType); + if (sheetsXmlString === undefined) { + throw new Error(sheetsNotFoundErr); + } + + const sheetString: string = xmlInnerPartsUtils.randomizeWorksheetUUID(sheetsXmlString); + zip.file(sheetsXmlPath, sheetString); + + const workbookXmlString: string | undefined = await zip.file(workbookXmlPath)?.async(textResultType); + if (workbookXmlString === undefined) { + throw new Error(workbookNotFoundErr); + } + + const workbookString: string = xmlInnerPartsUtils.randomizeWorkbookUUID(workbookXmlString); + zip.file(workbookXmlPath, workbookString); + + const tableXmlString: string | undefined = await zip.file(tableXmlPath)?.async(textResultType); + if (tableXmlString === undefined) { + throw new Error(tableNotFoundErr); + } + + const tableString: string = xmlInnerPartsUtils.randomizeTableUUID(tableXmlString); + zip.file(tableXmlPath, tableString); + + if (updateQueryTable) { + const connectionsXmlString: string | undefined = await zip.file(connectionsXmlPath)?.async(textResultType); + if (connectionsXmlString === undefined) { + throw new Error(connectionsNotFoundErr); + } + + const connectionXmlFileString = xmlInnerPartsUtils.randomizeConnectionsUUID(connectionsXmlString); + zip.file(connectionsXmlPath, connectionXmlFileString); + + const queryTableXmlString: string | undefined = await zip.file(queryTableXmlPath)?.async(textResultType); + if (queryTableXmlString === undefined) { + throw new Error(queryTableNotFoundErr); + } + + const queryTableString: string = xmlInnerPartsUtils.randomizeQueryTableUUID(queryTableXmlString); + zip.file(queryTableXmlPath, queryTableString); + } +}; + export default { updateWorkbookInitialDataIfNeeded, updateWorkbookPowerQueryDocument, updateWorkbookSingleQueryAttributes, + updateWorkbookGeneratedUUIDs, }; diff --git a/src/workbookManager.ts b/src/workbookManager.ts index b445806..d849feb 100644 --- a/src/workbookManager.ts +++ b/src/workbookManager.ts @@ -44,6 +44,7 @@ export const generateTableWorkbookFromGrid = async (grid: Grid, docProps?: DocPr throw new Error(tableNotFoundErr); } + await xmlPartsUtils.updateWorkbookGeneratedUUIDs(zip); await xmlPartsUtils.updateWorkbookInitialDataIfNeeded(zip, docProps, tableData); return await zip.generateAsync({ @@ -57,6 +58,7 @@ const generateSingleQueryWorkbookFromZip = async (zip: JSZip, query: QueryInfo, query.queryName = defaults.queryName; } + await xmlPartsUtils.updateWorkbookGeneratedUUIDs(zip, true /*updateQueryTable*/); await xmlPartsUtils.updateWorkbookPowerQueryDocument(zip, query.queryName, generateSingleQueryMashup(query.queryName, query.queryMashup)); await xmlPartsUtils.updateWorkbookSingleQueryAttributes(zip, query.queryName, query.refreshOnOpen); await xmlPartsUtils.updateWorkbookInitialDataIfNeeded(zip, docProps, tableData, true /*updateQueryTable*/);