{"version":"0.3.0","body":"function main(workbook: ExcelScript.Workbook) {\n    var intSheetNo: number;\n    var intSheetIndexToMap: number;\n    var strNewRangeAddress: string;\n    var strControlCode: string;\n    var booResetMode: boolean;\n    var booDebugMode: boolean;\n    var strSourceIndexSheetAddress: string;\n    var intConsoleLoggingVerbosityLevel: number;\n    var strUnlockPassword: string;\n    var niObfuscatedPasswordCell: ExcelScript.NamedItem;\n    var intPasswordStorageSheetIndex: number;\n    var strObfuscatedPassword: string;\n    var intObfuscationKey: number;\n\n    var aintSheetIndexNumbers: number[];\n    var astrSheetNames: string[];\n    var astrSourceTableAddresses: string[];\n    var astrDestinationTables: string[];\n    var astrDataTableTypes: string[];\n    var aintSkipRows: number[];\n    var abooSheetProtection: boolean[];\n    var abooSheetProtectionAllowRowInsertion: boolean[];\n    var abooSheetProtectionAllowColFormatting: boolean[];\n\n    var astrQuestionIndexes: string[];\n    var astrQuestionTransferCodes: string[];\n    var abooQuestionTransferYN: boolean[];\n    var astrWorkbookSettingValues: string[];\n\n    const intStartTimeMS = Date.now() as number;\n    const calcModeAtStart = workbook.getApplication().getCalculationMode();\n\n    /*\n        Add in here the names of any named ranges used during sheet processing which takes place outside the try ... catch block\n        It is not necessary to add the names of ranges used in the initial set up because those are individually tested, and handled within the try...catch block\n    */\n    const astrCriticalNamedRanges: string[] = [`txRefListSheetNum`, `txRefMatrixDataPresentColNum`, `txRefMatrixDataDetectionCol`, `txSourceMatrixDataPresentCol`, `txRefMatrixLastRowOfData`, `txRefUnmappedSourceRangeMarker`];\n\n    // hard-coding these setting names and defaults here prevents need for use of INDIRECT in formulae which will slow down sheet recalculation\n    const astrWorkbookSettingNames: string[] = [`selectedLang`, `selectedLang2`, `selectedLangPS`, `selectedAreaUnit`, `selectedWoodVolUnit`, `selectedNtfpWeightUnit`, `selectedPesticideVolumeUnit`, `selectedPnCVersion`];\n    const astrWorkbookSettingDefaults: string[] = [`EN`, `ES`, `EN`, `ha`, `m3`, `metric tonnes`, `l`, `V5`];\n\n    var wsTxData: ExcelScript.Worksheet;\n    var rngNextProcessMessageCell: ExcelScript.Range;\n    var rngTxWorkbookSettings: ExcelScript.Range;\n    var tblTransferSheets: ExcelScript.Table;\n    var tblQuestions: ExcelScript.Table;\n    var tblDefaultFormula: ExcelScript.Table;\n    var astrDefaultFormulaeIndexes: string[];\n    var astrDefaultFormulaeDefinitions: string[];\n\n    /* *** INITIALISE SCRIPT INSIDE TRY...CATCH BLOCK *** */\n    try {\n        intConsoleLoggingVerbosityLevel = findNamedItemValueNumber(`txLoggingVerbosityLevel`, 0);\n        rngNextProcessMessageCell = findNamedItem(`txProcessingStatusMessageAreaStart`, `Status message area`).getRange();\n\n        // Reset exit status message & processing status message area\n        workbook.getNamedItem(`txRefExitStatusMessage`).getRange().getCell(0, 0).setValue(``);\n        rngNextProcessMessageCell.getExtendedRange(ExcelScript.KeyboardDirection.down).clear(ExcelScript.ClearApplyTo.contents);\n\n        // Log start time\n        rngNextProcessMessageCell.setValue((new Date(intStartTimeMS)).toTimeString() + ` : Starting`);\n        rngNextProcessMessageCell = rngNextProcessMessageCell.getOffsetRange(1, 0);\n\n        // Ensure cells are updated and save current calc mode\n        recalcNow();\n\n        strControlCode = findNamedItem(`txEnteredControlCode`, `Control Code`).getRange().getText();\n        booResetMode = (strControlCode == `RESET`);\n        booDebugMode = (strControlCode == `DEBUG`);\n        if (booResetMode) logProcessStatus(`Resetting spreadsheet`);\n\n        wsTxData = workbook.getWorksheet(`Tx Data`);\n        if (wsTxData == undefined) {\n            exitWithError(`Could not find Tx Data worksheet (normally hidden)`);\n            return;\n        }\n\n        // Check the source file is available\n        if (findNamedItemValueNumber(`txCheckFoundSourceFile`, 0) == 0 && !booResetMode) {\n            exitWithError(`Source file could not be found - exiting now. Don't forget the .xlsx on the end.`);\n            return;\n        }\n\n        // Set calc mode to manual\n        workbook.getApplication().setCalculationMode(ExcelScript.CalculationMode.manual);\n        logToConsole(`Calc mode set to manual`, 2);\n\n        // Define key objects\n        strSourceIndexSheetAddress = findNamedItem(`txRefSourceIndexAddress`, `Source file index address`).getRange().getText() as string;\n        tblTransferSheets = workbook.getTable(`conttblSheets_n_DataTables`);\n        if (!tblTransferSheets) throw new Error(`Could not find conttblSheets_n_DataTables - exiting now`);\n        aintSheetIndexNumbers = getColumnValuesAsNumbers(tblTransferSheets, `Number`);\n        logToConsole(`Sheet index numbers ` + aintSheetIndexNumbers.toString(), 2);\n        astrSheetNames = getColumnValuesAsStrings(tblTransferSheets, `Sheet Name`);\n        astrSourceTableAddresses = getColumnValuesAsStrings(tblTransferSheets, `Tx Source Table Address`);\n        astrDestinationTables = getColumnValuesAsStrings(tblTransferSheets, `Data Table`);\n        astrDataTableTypes = getColumnValuesAsStrings(tblTransferSheets, `Type`);\n        aintSkipRows = getColumnValuesAsNumbers(tblTransferSheets, `Tx Skip Rows`);\n        abooSheetProtection = getColumnValuesAsBooleans(tblTransferSheets, `Protect`);\n        abooSheetProtectionAllowRowInsertion = getColumnValuesAsBooleans(tblTransferSheets, `Insert Rows Allowed`);\n        abooSheetProtectionAllowColFormatting = getColumnValuesAsBooleans(tblTransferSheets, `Col Formatting Allowed`);\n\n        tblQuestions = workbook.getTable(`conttblQuestions`);\n        if (!tblQuestions) throw new Error(`Could not find conttblQuestions - exiting now`);\n        astrQuestionIndexes = getColumnValuesAsStrings(tblQuestions, `Index`);\n        astrQuestionTransferCodes = getColumnValuesAsStrings(tblQuestions, `Transfer`);\n        abooQuestionTransferYN = astrQuestionTransferCodes.map(s => (s.toUpperCase() == `TRUE` || s.startsWith(`With Unit`)));\n\n        tblDefaultFormula = workbook.getTable(`txtblDefaultFormulae`);\n        if (!tblDefaultFormula) throw new Error(`Could not find txtblDefaultFormulae - exiting now`);\n        astrDefaultFormulaeIndexes = getColumnValuesAsStrings(tblDefaultFormula, `Index`);\n        astrDefaultFormulaeDefinitions = getColumnValuesAsStrings(tblDefaultFormula, `Default Formula Definition`);\n\n        // Unlock sheets if password provided\n        intPasswordStorageSheetIndex = -1;\n        //strUnlockPassword = findNamedItem(`txUnlockPassword`, `Unlock Code`).getRange().getValue().toString();\n        //if ( strUnlockPassword == `` )\n        {\n            niObfuscatedPasswordCell = findNamedItem(`contObfuscatedPW`, `Transfer Unlock Code`);\n            strObfuscatedPassword = niObfuscatedPasswordCell.getRange().getValue().toString();\n            //logToConsole(`Obfuscated password is ${strObfuscatedPassword}`, 1);\n            intObfuscationKey = findNamedItemValueNumber(`contObfuscationKey`, 0);\n            //logToConsole(`Obfuscation key is ${intObfuscationKey}`, 1);\n            if (strObfuscatedPassword != `` && intObfuscationKey > 0) {\n                intPasswordStorageSheetIndex = niObfuscatedPasswordCell.getRange().getWorksheet().getPosition();\n\n                // Convert hex string to byte array\n                var aintObfuscatedBytes: number[] = [];\n                for (let i = 0; i < strObfuscatedPassword.length; i += 2)\n                    aintObfuscatedBytes.push(parseInt(strObfuscatedPassword.substr(i, 2), 16));\n\n                // XOR each byte with the key\n                let decryptedBytes = aintObfuscatedBytes.map(byte => byte ^ intObfuscationKey) as number[];\n\n                // Convert back to string\n                strUnlockPassword = String.fromCharCode(...decryptedBytes);\n            }\n        }\n        if (strUnlockPassword != ``) {\n            logToConsole(`Unlocking worksheets`, 1);\n            //workbook.getNamedItem(`txUnlockPassword`).getRange().clear(ExcelScript.ClearApplyTo.contents);\n            for (intSheetNo = 0; intSheetNo < aintSheetIndexNumbers.length; intSheetNo++)\n                // Do not attempt to unlock Changes Log sheet which is locked with a different password\n                if (intSheetNo != intPasswordStorageSheetIndex)\n                    if (abooSheetProtection[intSheetNo]) unprotectSheet(astrSheetNames[intSheetNo]);\n        }\n\n        /* *** Initialization completed *** */\n\n        /* *** COPY WORKBOOK LEVEL SETTINGS *** */\n        var rngOutputSetting: ExcelScript.Range;\n        if (booResetMode) {\n            for (let i = 0; i < astrWorkbookSettingNames.length; i++) {\n                rngOutputSetting = findNamedItem(astrWorkbookSettingNames[i], `output cell for setting ` + astrWorkbookSettingNames[i]).getRange();\n                rngOutputSetting.setFormula(astrWorkbookSettingDefaults[i]);\n            }\n            astrWorkbookSettingValues = astrWorkbookSettingDefaults;\n        }\n        else {\n            logProcessStatus(`Copying workbook level settings`);\n            rngTxWorkbookSettings = findNamedItem(`txWorkbookSettings`, `workbook settings transfer space`).getRange();\n\n            // this loop captures all the setting values from the source sheet and writes out references to them in Tx Control\n            for (let i = 0; i < astrWorkbookSettingNames.length; i++) {\n                strNewRangeAddress = `=` + strSourceIndexSheetAddress + astrWorkbookSettingNames[i];\n                logToConsole(`Transferring workbook setting ${strNewRangeAddress}`, 3);\n                rngTxWorkbookSettings.getCell(i, 0).setFormula(strNewRangeAddress);\n            }\n            recalcNow();    // recalc to force those formulae to execute\n\n            // this loop copies all those settings from Tx Control into this workbook's settings\n            astrWorkbookSettingValues = rngTxWorkbookSettings.getValues().map(r => r[0].toString());\n            rngTxWorkbookSettings.clear(ExcelScript.ClearApplyTo.contents);\n            for (let i = 0; i < astrWorkbookSettingNames.length; i++) {\n                logToConsole(`Setting ${i} for ${astrWorkbookSettingNames[i]} has value ${astrWorkbookSettingValues[i]}`, 3);\n\n                rngOutputSetting = findNamedItem(astrWorkbookSettingNames[i], `output cell for setting ` + astrWorkbookSettingNames[i]).getRange();\n\n                // check for empty strings or errors (meaning source sheet did not have this setting)\n                if (astrWorkbookSettingValues[i] != `` && !astrWorkbookSettingValues[i].startsWith(`#`))\n                    rngOutputSetting.setFormula(astrWorkbookSettingValues[i]);\n                else\n                    // setting was not found in source so look up the default here\n                    astrWorkbookSettingValues[i] = rngOutputSetting.getText();\n            }\n            // Note those unit defaults are saved for future reference and available via the getSelectedDefaultUnit function\n        }\n\n        // Check now for various named ranges that will be needed during sheet processing\n        for (let i = 0; i < astrCriticalNamedRanges.length; i++) {\n            if (!workbook.getNamedItem(astrCriticalNamedRanges[i])) {\n                exitWithError(`Could not locate in this spreadsheet the range named ${astrCriticalNamedRanges[i]} that is critical for this data transfer script`);\n                return;\n            }\n        }\n    }\n    catch (exc) {\n        exitWithError(`Error encountered: ${exc.code} on line ${exc.line} ${exc.type} ${exc.method} : ${exc.message}`);\n        throw exc;\n    }\n\n    /* \n        *** DATA SHEET PROCESSING ***\n    \n        This runs outside a try...catch block for performance reasons) \n        see https://learn.microsoft.com/en-us/office/dev/scripts/develop/web-client-performance for more detail\n    */\n    //logProcessStatus(`Starting processing of data sheets`);\n\n    let intStartingSheet = Number(findNamedItemValueString(`txEnteredStartSheet`, `0`)) as number;\n    let intEndSheet = Number(findNamedItemValueString(`txEnteredEndSheet`, `30`)) as number;\n    let strExcludeSheets = findNamedItemValueString(`txEnteredExcludeSheets`, ``) as string;\n    let aintExcludeSheets = strExcludeSheets.split(`,`).map(s => Number(s)) as number[];\n    let strErrorState = `` as string;\n    for (intSheetNo = 0; intSheetNo < aintSheetIndexNumbers.length; intSheetNo++) {\n        intSheetIndexToMap = aintSheetIndexNumbers[intSheetNo];\n        if (intSheetIndexToMap > 0) {\n            if (intSheetIndexToMap >= intStartingSheet && intSheetIndexToMap <= intEndSheet\n                && aintExcludeSheets.indexOf(intSheetIndexToMap) == -1) {\n                if (!booResetMode) {\n                    // Ensure the data transfer sheet is clear\n                    wsTxData.getRange().clear(ExcelScript.ClearApplyTo.all);\n                    // Some table headings have format set to Text, which prevent execution of the formula for the next sheet, so need to reset to General\n                    wsTxData.getRange().setNumberFormatLocal(\"General\");\n\n                    workbook.getNamedItem(`txRefListSheetNum`).getRange().setValue(intSheetIndexToMap);\n                    //recalcNow(); - recalc not needed here since result will only be used in Matrix processing after a recalc is triggered for other reasons\n                }\n\n                switch (astrDataTableTypes[intSheetNo]) {\n                    case `List`:\n                        strErrorState = transferListTable(intSheetIndexToMap);\n                        break;\n\n                    case `Matrix`:\n                        strErrorState = transferMatrixTable(intSheetIndexToMap);\n                        break;\n\n                    case `P&C`:\n                        // note that P&C version will already have been copied over with workbook level settings\n                        strErrorState = transferListTable(intSheetIndexToMap, `I18.03`, false);\n                        break;\n\n                    default:\n                        strErrorState = ``\n                }\n\n                if (strErrorState != ``) {\n                    exitWithError(strErrorState);\n                    return;\n                }\n\n\n                recalcNow();\n            }\n            //else\n            //logProcessStatus(`Skipping sheet no. ${intSheetNumberToMap}`);\n        }\n        else\n            logToConsole(`Sheet # ${intSheetNo} is not for processing`, 2);\n    }\n\n    // Re-lock before exiting\n    if (strUnlockPassword != ``) {\n        logToConsole(`Relocking worksheets`, 1);\n        for (intSheetNo = 0; intSheetNo < aintSheetIndexNumbers.length; intSheetNo++)\n            if (abooSheetProtection[intSheetNo]) reprotectSheet(intSheetNo);\n    }\n\n    // Look for reset prevention control code\n    if (booDebugMode)\n        setExitMessage(`Ending debug mode - calculation mode may be left in Manual`);\n    else {\n        // Otherwise reset working data and return calculation mode to what it was\n        if (!booResetMode) logToConsole(`Completed processing, resetting workings`, 1);\n        resetWorkingData();\n\n        // Set the focus to the first cell on each sheet\n        var wsOutput: ExcelScript.Worksheet;\n        var niCellFocus: ExcelScript.NamedItem;\n        for (intSheetNo = 0; intSheetNo < aintSheetIndexNumbers.length; intSheetNo++) {\n            if (intSheetIndexToMap >= intStartingSheet && intSheetIndexToMap <= intEndSheet\n                && aintExcludeSheets.indexOf(intSheetIndexToMap) == -1) {\n\n                wsOutput = workbook.getWorksheet(astrSheetNames[intSheetNo]);\n                if (wsOutput) {\n                    niCellFocus = workbook.getWorksheet(astrSheetNames[intSheetNo]).getNamedItem(`sheetInitialFocus`);\n                    if (niCellFocus) niCellFocus.getRange().getCell(0, 0).select();\n                }\n                else\n                    logToConsole(`Could not find worksheet #${intSheetNo} called ${astrSheetNames[intSheetNo]} to set the cell focus`, 0);\n            }\n        }\n        // And then return us to Tx Control\n        workbook.getNamedItem('txRefExitStatusMessage').getRange().getCell(0, 0).select();\n\n        logToConsole(`Calc mode set back to ` + calcModeAtStart.toString(), 1);\n        if (!booResetMode) {\n            let niHideSheet = workbook.getNamedItem(`txHideSheetOnCompletion`);\n            if (niHideSheet) {\n                if (Boolean(niHideSheet.getRange().getValue())) {\n                    workbook.getWorksheet(`Index`).activate();\n                    workbook.getWorksheet(`Tx Control`).setVisibility(ExcelScript.SheetVisibility.hidden);\n                    logToConsole(`Hid Tx Control sheet`, 1);\n                }\n            }\n        }\n    }\n\n    workbook.getNamedItem('txRefExitStatusMessage').getRange().getCell(0, 0).setValue(`Completed successfully`);\n    return;\n\n    /*\n        This interface is used to organise data when mapping or resetting matrix sheets\n    */\n    interface MappedColumn {\n        questionIndex: string,\n        sourceColumnNo: number,\n        transferYN: boolean,\n        transferCode: string,\n        defaultUnit: string,\n        defaultFormula: string\n    };\n\n    function transferListTable(intSheetIndex: number, strInputsCol: string = `Inputs`, booCheckQuestions: boolean = true): string\n    /*\n        To efficiently get the questions from the Source file we remap two named ranges to point to the source table (index and input columns)\n        We then paste references to that data into the Tx Data sheet\n        Then we locate our output table and then loop over each row, copy-pasting-values into each cell when data is found\n\n        strInputsCol defaults to Inputs, but allows us to look for other options as needed for sheet 18 P&C\n        booCheckQuestions defaults to True, in which case errors will be returned if list rows are not in the master question list (Questions tab), but allows for sheet 18 P&C to list out criteria instead\n\n        Return value is blank string unless an error found\n    */ {\n        if (booResetMode) return resetListTable(intSheetIndex, strInputsCol, booCheckQuestions);\n\n        logProcessStatus(`Transferring data on list sheet ${intSheetIndex}`);\n\n        // Locate output table\n        let tblOutputTable = workbook.getTable(astrDestinationTables[intSheetNo]) as ExcelScript.Table;\n        let astrOutputIndex = getColumnValuesAsStrings(tblOutputTable, `Index`);\n        //console.log(`List output indexes ` + astrOutputIndex);\n        let colOutputData = tblOutputTable.getColumnByName(strInputsCol).getRangeBetweenHeaderAndTotal() as ExcelScript.Range;\n        var colOutputUnits: ExcelScript.Range;\n        let booHasUnitsColumn = (tblOutputTable.getColumn(`Units`) != undefined);\n        if (booHasUnitsColumn) colOutputUnits = tblOutputTable.getColumn(`Units`).getRangeBetweenHeaderAndTotal();\n\n        logToConsole(`Mapping ranges for list sheet index ${intSheetIndex} (# ${intSheetNo})`, 1);\n\n        /*\n            If the source table does not exist the wsTxData.getCell().setFormula() calls return an error\n                The argument is invalid or missing or has an incorrect format.\n            Unfortunately there is no way to check this in advance\n            So we have to surround this in a try...catch block\n        */\n        try {\n            strNewRangeAddress = `=` + astrSourceTableAddresses[intSheetNo] + `[Index]`;\n            logToConsole(`List table questions indexes source ${strNewRangeAddress}`, 2);\n            wsTxData.getCell(0, 0).setFormula(strNewRangeAddress);      // paste into col A\n\n            strNewRangeAddress = `=` + astrSourceTableAddresses[intSheetNo] + `[` + strInputsCol + `]`;\n            logToConsole(`List table input data source ${strNewRangeAddress}`, 2);\n            wsTxData.getCell(0, 1).setFormula(strNewRangeAddress);      // paste into col B\n\n            if (booHasUnitsColumn) {\n                strNewRangeAddress = `=` + astrSourceTableAddresses[intSheetNo] + `[Units]`;\n                logToConsole(`List table input units data source ${strNewRangeAddress}`, 2);\n                wsTxData.getCell(0, 2).setFormula(strNewRangeAddress);      // paste into col C\n            }\n\n            recalcNow();\n        }\n        catch (exc) {\n            logToConsole(`Error locating source list data for sheet #${intSheetIndex}: ${exc.code} on line ${exc.line} ${exc.type} ${exc.method} : ${exc.message}`, 1);\n            logProcessStatus(`Could not find Source List data for sheet #${intSheetIndex} - skipping to next sheet`);\n            return ``;\n        }\n\n        // Read all of that mapped data in\n        let aobjSourceListData = wsTxData.getUsedRange().getValues() as object[];\n        let astrSourceIndex = aobjSourceListData.map(r => r[0].toString()) as string[];\n        //console.log(`List source indexes ` + astrSourceIndex);\n        let aobjSourceEnteredData = aobjSourceListData.map(r => r[1]) as object[];\n        //console.log(`List source entered data:`);\n        //console.log(aobjSourceEnteredData);\n        var astrSelectedUnits: string[];\n        if (booHasUnitsColumn)\n            astrSelectedUnits = aobjSourceListData.map(r => r[2].toString()) as string[];\n        else\n            astrSelectedUnits = new Array(astrSourceIndex.length).fill(``);\n\n        logToConsole(`Looping over data in list sheet index ${intSheetIndex}, ` + (booCheckQuestions ? `checking questions` : `not checking questions`), 1);\n\n        var strIndex: string;\n        var intSourceRow: number;\n        var objInputValue: object;\n        var strInputValue: string;\n        var intMasterQuestionItemNo: number;\n        var strDefaultUnit: string;\n        for (let intOutputRow = 0; intOutputRow < astrOutputIndex.length; intOutputRow++) {\n            strIndex = astrOutputIndex[intOutputRow] as string;\n            intSourceRow = astrSourceIndex.indexOf(strIndex);\n            if (intSourceRow > -1) {\n                intMasterQuestionItemNo = astrQuestionIndexes.indexOf(strIndex);\n                logToConsole(`... loop ${intOutputRow}, index = ${strIndex}, question ref = ${intMasterQuestionItemNo}`, 3);\n                if (!booCheckQuestions || intMasterQuestionItemNo > -1) {\n                    strInputValue = aobjSourceEnteredData[intSourceRow].toString();\n                    if (!booCheckQuestions || abooQuestionTransferYN[intMasterQuestionItemNo]) {\n                        //console.log(`... loop #${intOutputRow} with index ${strIndex} to get data from source row ${intSourceRow} with value ${strInputValue}`);\n                        /*\n                            The spreadsheet signals unfound data as zero.\n                            We do this because Excel converts blank cells to zeroes in formulae results. We could check to see whether the source is blank first, but that is inefficient because it involves two references to the other file, which are relatively slow.\n                            This approach has risks because actual zeroes in the source data will then not be written to the transferred sheet. But actual zeroes will be relatively rare, and so in the trade-off between speed and completeness we opt here for speed.\n                        */\n                        if (strInputValue != `0`) {\n                            // paste in original object rather than string version\n                            colOutputData.getCell(intOutputRow, 0).setValue(aobjSourceEnteredData[intSourceRow]);\n\n                            // Now check if we also need to transfer a unit selection\n                            // (this may not be used in practice, as most List sheet questions have their units fixed to the default)\n                            if (booHasUnitsColumn && astrQuestionTransferCodes[intMasterQuestionItemNo].startsWith(`With Unit`)) {\n                                strDefaultUnit = getSelectedDefaultUnit(astrQuestionTransferCodes[intMasterQuestionItemNo]);\n                                if (astrSelectedUnits[intSourceRow] != strDefaultUnit) {\n                                    logToConsole(`... pasting in non-default unit selection (${astrSelectedUnits[intSourceRow]}) for question ${strIndex}`, 3);\n                                    colOutputUnits.getCell(intOutputRow, 0).setValue(astrSelectedUnits[intSourceRow]);\n                                }\n                            }\n                        }\n                    }\n                    else\n                        logToConsole(`... list question ${strIndex} is NOT transferrable`, 3);\n                }\n                else\n                    // should never get here if spreadsheet integrity is maintained\n                    return `Could not find master question reference for #${strIndex} - exiting now`;\n            }\n            else\n                logToConsole(`Loop #${intOutputRow} with index ${strIndex} has no matching source row`, 3);\n        }\n\n        // Return an empty string indicating completion\n        return ``;\n    }\n\n    function transferMatrixTable(intSheetIndex: number): string\n    /*\n        To efficiently get the questions from the Source file we remap two named ranges to point to the source table (headers and content)\n        We then paste references to that data into the Tx Data sheet\n        In order to be efficient we look up the exact number of rows and columns to copy over (allowing for blank rows)\n        We look for markers that data is present, so we can later skip over those blank rows\n        Then we locate our output table and map the columns from the source to the output\n        (the same questions may not be present in source and output and the order may differ)\n        With all of that preparation, we can then efficiently loop over each row and column, copy-pasting-values into each cell when data is found\n\n        Return value is blank string unless an error found\n    */ {\n        if (booResetMode) return resetMatrixTable(intSheetIndex);\n\n        logProcessStatus(`Transferring data on matrix sheet ${intSheetIndex}`);\n        logToConsole(`Mapping ranges for matrix sheet index ${intSheetIndex} (# ${intSheetNo})`, 1);\n\n        /*\n            If the source table does not exist the wsTxData.getCell().setFormula() calls return an error\n                The argument is invalid or missing or has an incorrect format.\n            Unfortunately there is no way to check this in advance\n            So we have to surround this in a try...catch block\n        */\n        try {\n            strNewRangeAddress = `=` + astrSourceTableAddresses[intSheetNo] + `[#Headers]`;\n            logToConsole(`Matrix table questions indexes source ${strNewRangeAddress}`, 2);\n            wsTxData.getCell(0, 0).setFormula(strNewRangeAddress);\n            recalcNow();\n        }\n        catch (exc) {\n            logToConsole(`Error locating source matrix headers for sheet #${intSheetIndex}: ${exc.code} on line ${exc.line} ${exc.type} ${exc.method} : ${exc.message}`, 1);\n            logProcessStatus(`Could not find Source Matrix data for sheet #${intSheetIndex} - skipping to next sheet`);\n            return ``;\n        }\n\n        let rngMatrixColumnHeadings = wsTxData.getRange(`$1:$1`).getUsedRange() as ExcelScript.Range;\n        // getValues() returns an array of arrays - we need the zero row (there won't be any others) to get the array of column headers\n        let astrSourceIndex = rngMatrixColumnHeadings.getValues()[0].map(v => v.toString()) as string[];\n        //console.log(`Input indexes ` + astrSourceIndex);\n        let intMatrixCols = astrSourceIndex.length as number;\n        logToConsole(`Matrix on sheet #${intSheetIndex} has ${intMatrixCols} columns`, 2);\n\n        let booDataPresentColumnFoundInSource = (findNamedItemValueNumber(`txRefMatrixDataPresentColNum`, 0) > 0) as boolean;\n        let strDataDetectionColName = findNamedItemValueString(`txRefMatrixDataDetectionCol`, `Data Present`) as string;\n        strNewRangeAddress = `=` + astrSourceTableAddresses[intSheetNo] + `[` + strDataDetectionColName + `]\n        `;\n        //console.log(`Matrix table data present col ${strNewRangeAddress}`);\n        workbook.getNamedItem(`txSourceMatrixDataPresentCol`).setFormula(strNewRangeAddress);\n        recalcNow();\n        let intMatrixRows = findNamedItemValueNumber(`txRefMatrixLastRowOfData`, -1) as number;\n        logToConsole(`Matrix on sheet #${intSheetIndex} has ${intMatrixRows} rows`, 2);\n\n        if (intMatrixRows == -1) {\n            logToConsole(`No data to copy over so exiting matrix transfer for sheet ${intSheetIndex}`, 1);\n            return ``;\n        }\n\n        /*\n            If the source table does not exist the wsTxData.getCell().setFormula() calls return an error\n                The argument is invalid or missing or has an incorrect format.\n            Unfortunately there is no way to check this in advance\n            So we have to surround this in a try...catch block\n        */\n        try {\n            strNewRangeAddress = `=TAKE(` + astrSourceTableAddresses[intSheetNo] + `, ${intMatrixRows} , ${intMatrixCols})`;\n            logToConsole(`Matrix table main data source ${strNewRangeAddress}`, 2);\n            wsTxData.getCell(1, 0).setFormula(strNewRangeAddress);\n            recalcNow();\n        }\n        catch (exc) {\n            logToConsole(`Error locating source matrix data for sheet #${intSheetIndex}: ${exc.code} on line ${exc.line} ${exc.type} ${exc.method} : ${exc.message}`, 1);\n            logProcessStatus(`Could not find Source Matrix data for sheet #${intSheetIndex} - skipping to next sheet`);\n            return ``;\n        }\n\n        logToConsole(`Prepping field variables for copying data from matrix sheet ${intSheetIndex}`, 1);\n\n        // Look for values from cell A2 onwards (note that we already checked we have at least one row of data)\n        let aMatrixInputs = wsTxData.getCell(1, 0).getBoundingRect(wsTxData.getUsedRange().getLastCell()).getValues() as object[];\n\n        // Set up row-by-row data detection mechanism\n        let intDataDetectionColNo = astrSourceIndex.indexOf(strDataDetectionColName);\n        let varDataAbsentMarker = (booDataPresentColumnFoundInSource ? false : 0);\n        //console.log(`Data Detection Marker is ${varDataAbsentMarker}`);\n\n        let tblOutputTable = workbook.getTable(astrDestinationTables[intSheetNo]) as ExcelScript.Table;\n        // getValues() returns an array of arrays - we need the zero row (there won't be any others) to get the array of column headers\n        let astrOutputIndex = tblOutputTable.getHeaderRowRange().getValues()[0].map(v => v.toString());\n        //console.log(`Output indexes ` + astrOutputIndex);\n        let rngOutputCells = tblOutputTable.getRangeBetweenHeaderAndTotal() as ExcelScript.Range;\n        logToConsole(`Output range is ` + rngOutputCells.getAddress(), 2);\n\n        // Now we map the columns together once, so then we can just loop through for each row\n        let aMappedColumns: MappedColumn[] = [];\n        var intOutputCol: number;\n        var strIndex: string;\n        var intMasterQuestionItemNo: number;\n        var mc: MappedColumn;\n        for (intOutputCol = 0; intOutputCol < astrOutputIndex.length; intOutputCol++) {\n            strIndex = astrOutputIndex[intOutputCol];\n            intMasterQuestionItemNo = astrQuestionIndexes.indexOf(strIndex);\n\n            if (intMasterQuestionItemNo > -1)\n                mc = {\n                    questionIndex: strIndex,\n                    sourceColumnNo: astrSourceIndex.indexOf(strIndex),\n                    transferYN: abooQuestionTransferYN[intMasterQuestionItemNo],\n                    transferCode: astrQuestionTransferCodes[intMasterQuestionItemNo],\n                    defaultUnit: getSelectedDefaultUnit(astrQuestionTransferCodes[intMasterQuestionItemNo]),\n                    defaultFormula: getDefaultFormula(astrQuestionTransferCodes[intMasterQuestionItemNo], strIndex)\n                };\n            else\n                mc = {\n                    questionIndex: strIndex,\n                    sourceColumnNo: -1,\n                    transferYN: false,\n                    transferCode: ``,\n                    defaultUnit: ``,\n                    defaultFormula: ``\n                };\n\n            aMappedColumns.push(mc);\n        }\n        //console.log(`Mapped columns:`);\n        //console.log(aMappedColumns);\n\n        logToConsole(`Looping over data in matrix sheet ${intSheetIndex}`, 2);\n        logToConsole(`... starting at row ${aintSkipRows[intSheetNo]}`, 3);\n\n        var rngOutputRow: ExcelScript.Range;\n        var rngOutputCell: ExcelScript.Range;\n        var intInputCol: number;\n        var strInputValue: string;\n        for (let intOutputRow = aintSkipRows[intSheetNo]; intOutputRow < intMatrixRows; intOutputRow++) {\n            if (aMatrixInputs[intOutputRow][intDataDetectionColNo] != varDataAbsentMarker) {\n                rngOutputRow = rngOutputCells.getRow(intOutputRow);\n                logToConsole(`Writing out row ${intOutputRow} with address ` + rngOutputRow.getAddress(), 3);\n                for (intOutputCol = 0; intOutputCol < aMappedColumns.length; intOutputCol++) {\n                    intInputCol = aMappedColumns[intOutputCol].sourceColumnNo;\n                    if (intInputCol > -1) {\n                        strInputValue = aMatrixInputs[intOutputRow][intInputCol].toString();\n                        if (aMappedColumns[intOutputCol].transferYN) {\n                            logToConsole(`... col ${intOutputCol} with index ` + aMappedColumns[intOutputCol].questionIndex + ` has value ${strInputValue} and is transferrable to col ${intOutputCol}`, 3);\n                            logToConsole(`... ... placing into ${rngOutputCells.getCell(intOutputRow, intOutputCol).getAddress()}`, 3);\n                            logToConsole(`... ... with data ${aMatrixInputs[intOutputRow][intInputCol]}`, 3);\n\n                            /*\n                                The spreadsheet signals unfound data as zero.\n                                We do this because Excel converts blank cells to zeroes in formulae results. We could check to see whether the source is blank first, but that is inefficient because it involves two references to the other file, which are relatively slow.\n                                This approach has risks because actual zeroes in the source data will then not be written to the transferred sheet. But actual zeroes will be relatively rare, and so in the trade-off between speed and completeness we opt here for speed.\n                            */\n                            if (strInputValue != `0`)\n                                //console.log(aMatrixInputs[intOutputRow][intInputCol]);\n                                // paste in original object rather than string version\n                                rngOutputCells.getCell(intOutputRow, intOutputCol).setValue(aMatrixInputs[intOutputRow][intInputCol]);\n                            else if (aMappedColumns[intOutputCol].defaultFormula != ``)\n                                // if there should be a default formula here we paste it in just to be safe\n                                rngOutputCells.getCell(intOutputRow, intOutputCol).setFormula(aMappedColumns[intOutputCol].defaultFormula);\n                            //else\n                            //console.log(`... ... but data is zero`);\n                        }\n                        else if (aMappedColumns[intOutputCol].transferCode.startsWith(`Unit`)) {\n                            // We do not need to worry here about cells locked to the default, because such columns should have Transfer Code = FALSE\n                            if (strInputValue != `0`) {\n                                if (strInputValue != aMappedColumns[intOutputCol].defaultUnit) {\n                                    rngOutputCells.getCell(intOutputRow, intOutputCol).setValue(strInputValue);\n                                    //console.log(`... col ${intOutputCol} with index ` + aMappedColumns[intOutputCol].questionIndex + ` is a unit selection - selected value ${strInputValue} differs from default ${aMappedColumns[intOutputCol].defaultUnit}, so have over-written`);\n                                }\n                                else {\n                                    rngOutputCells.getCell(intOutputRow, intOutputCol).setFormula(aMappedColumns[intOutputCol].defaultFormula);\n                                    //console.log(`... col ${intOutputCol} with index ` + aMappedColumns[intOutputCol].questionIndex + ` is a unit selection - selected value ${strInputValue} is same as default so inserting formula referencing default`);\n                                }\n                            }\n                        }\n                        else\n                            logToConsole(`... col ${intOutputCol} with index ` + aMappedColumns[intOutputCol].questionIndex + ` is NOT transferrable`, 3);\n                    }\n                    else\n                        logToConsole(`... no input column for col ${intOutputCol} with index ` + aMappedColumns[intOutputCol].questionIndex, 3);\n                }\n            }\n            else\n                logToConsole(`... No data to write out in row ${intOutputRow}`, 3);\n        }\n\n        // Return an empty string indicating completion\n        return ``;\n    }\n\n    function resetListTable(intSheetIndex: number, strInputsCol: string = `Inputs`, booCheckQuestions: boolean = true): string {\n        logToConsole(`Resetting data on list sheet index ${intSheetIndex}`, 1);\n\n        let tblOutputTable = workbook.getTable(astrDestinationTables[intSheetNo]) as ExcelScript.Table;\n        let astrOutputIndex = getColumnValuesAsStrings(tblOutputTable, `Index`) as string[];\n        let colOutputData = tblOutputTable.getColumnByName(strInputsCol).getRangeBetweenHeaderAndTotal() as ExcelScript.Range;\n        var colOutputUnits: ExcelScript.Range;\n        var astrSelectedUnitFormulae: string[];\n        let booHasUnitsColumn: boolean = (tblOutputTable.getColumn(`Units`) != undefined);\n        if (booHasUnitsColumn) {\n            colOutputUnits = tblOutputTable.getColumn(`Units`).getRangeBetweenHeaderAndTotal();\n            astrSelectedUnitFormulae = colOutputUnits.getFormulas().map(r => r[0].toString());\n        }\n\n        var strIndex: string;\n        var intMasterQuestionItemNo: number;\n        var rngCell: ExcelScript.Range;\n        var strDefaultUnit: string;\n        // Iterate through each row in the 'Inputs' column and clear its value\n        for (let intOutputRow = 0; intOutputRow < astrOutputIndex.length; intOutputRow++) {\n            strIndex = astrOutputIndex[intOutputRow] as string;\n            intMasterQuestionItemNo = astrQuestionIndexes.indexOf(strIndex);\n            if (!booCheckQuestions || abooQuestionTransferYN[intMasterQuestionItemNo]) {\n                //console.log(`Output row: ${intOutputRow} has index ${strIndex}, master item # ${intMasterQuestionItemNo}, and tx code ${astrQuestionTransferCodes[intMasterQuestionItemNo]}`);\n                rngCell = colOutputData.getCell(intOutputRow, 0);\n                rngCell.clear(ExcelScript.ClearApplyTo.contents);\n\n                // Now check if we also need to reset the unit selection\n                // (this may not be used in practice, as most List sheet questions have their units fixed to the default)\n                if (booHasUnitsColumn && astrQuestionTransferCodes[intMasterQuestionItemNo].startsWith(`With Unit`) && !astrSelectedUnitFormulae[intOutputRow].startsWith(`=`) && !isLocked(rngCell)) {\n                    logToConsole(`... resetting unit selection (${astrSelectedUnitFormulae[intOutputRow]}) for question ${strIndex}`, 3);\n                    rngCell.setFormula(getDefaultFormula(astrQuestionTransferCodes[intMasterQuestionItemNo], strIndex));\n                }\n            }\n        }\n\n        // Return an empty string indicating completion\n        return ``;\n    }\n\n    function resetMatrixTable(intSheetIndex: number): string {\n        logToConsole(`Resetting data on matrix sheet index ${intSheetIndex}`, 1);\n\n        // Set the formula for the range to reference the data detection column - note we use Destination table column for this rather than the Source table column when transferring data, and because of that we know that we can always rely upon the Data Present column being there\n        strNewRangeAddress = `=` + astrDestinationTables[intSheetNo] + `[Data Present]`;\n        workbook.getNamedItem(`txOutputMatrixDataPresentCol`).setFormula(strNewRangeAddress);\n        recalcNow(); // Ensure formulas are updated\n\n        // Get the number of matrix rows from a named item value\n        let intMatrixRows = findNamedItemValueNumber(`txRefOutputMatrixLastRowOfData`, -1) as number;\n        logToConsole(`Matrix on sheet ${intSheetIndex} has ${intMatrixRows} rows`, 2);\n\n        // Exit early if no data is found in the matrix\n        if (intMatrixRows == -1) {\n            //throw  new Error(`No data to reset on sheet ${intSheetNum}`);\n            logToConsole(`No data to reset on sheet ${intSheetIndex}`, 2);\n            return ``;\n        }\n\n        logToConsole(`Prepping field variables for resetting data on matrix sheet ${intSheetIndex}`, 2);\n\n        // Retrieve the output table and its headers\n        let tblOutputTable = workbook.getTable(astrDestinationTables[intSheetNo]) as ExcelScript.Table;\n        let astrOutputIndex = tblOutputTable.getHeaderRowRange().getValues()[0].map(v => v.toString()) as string[];\n        let rngOutputCells = tblOutputTable.getRangeBetweenHeaderAndTotal() as ExcelScript.Range;\n\n        // Now we map the columns together once, so then we can just loop through for each row\n        logToConsole(`${astrOutputIndex.length} fields to map starting with ${astrOutputIndex[0]}`, 3);\n        let aMappedColumns: MappedColumn[] = [];\n        var intOutputCol: number;\n        var strIndex: string;\n        var intMasterQuestionItemNo: number;\n        var mc: MappedColumn;\n        for (intOutputCol = 0; intOutputCol < astrOutputIndex.length; intOutputCol++) {\n            strIndex = astrOutputIndex[intOutputCol];\n            intMasterQuestionItemNo = astrQuestionIndexes.indexOf(strIndex);\n            if (intMasterQuestionItemNo > -1)\n                mc = {\n                    questionIndex: strIndex,\n                    sourceColumnNo: 0,\n                    transferYN: abooQuestionTransferYN[intMasterQuestionItemNo],\n                    transferCode: astrQuestionTransferCodes[intMasterQuestionItemNo],\n                    defaultUnit: getSelectedDefaultUnit(astrQuestionTransferCodes[intMasterQuestionItemNo]),\n                    defaultFormula: getDefaultFormula(astrQuestionTransferCodes[intMasterQuestionItemNo], strIndex)\n                };\n            else\n                mc = {\n                    questionIndex: strIndex,\n                    sourceColumnNo: 0,\n                    transferYN: false,\n                    transferCode: ``,\n                    defaultUnit: ``,\n                    defaultFormula: ``\n                };\n            aMappedColumns.push(mc);\n        }\n\n        logToConsole(`Looping over data in matrix sheet ${intSheetIndex}: ${rngOutputCells.getAddress()}`, 2);\n        var rngOutputCell: ExcelScript.Range;\n        // Iterate through each row and column to clear all data in the output range\n        for (let intOutputRow = aintSkipRows[intSheetNo]; intOutputRow < intMatrixRows; intOutputRow++) {\n            for (intOutputCol = 0; intOutputCol < aMappedColumns.length; intOutputCol++) {\n                logToConsole(`Resetting R${intOutputRow} C${intOutputCol} : ${aMappedColumns[intOutputCol].transferYN}, ${aMappedColumns[intOutputCol].transferCode}`, 3);\n                if (aMappedColumns[intOutputCol].transferYN) {\n                    if (aMappedColumns[intOutputCol].defaultFormula == ``)\n                        rngOutputCells.getCell(intOutputRow, intOutputCol).clear(ExcelScript.ClearApplyTo.contents);\n                    else\n                        rngOutputCells.getCell(intOutputRow, intOutputCol).setFormula(aMappedColumns[intOutputCol].defaultFormula);\n                }\n                else if (aMappedColumns[intOutputCol].transferCode.startsWith(`Unit`)) {\n                    // We do not need to worry here about cells locked to the default, because such columns should have Transfer Code = FALSE\n                    rngOutputCells.getCell(intOutputRow, intOutputCol).setFormula(aMappedColumns[intOutputCol].defaultFormula);\n                }\n            }\n        }\n\n        // Return an empty string indicating completion\n        return ``;\n    }\n\n    function recalcNow() { workbook.getApplication().calculate(ExcelScript.CalculationType.recalculate); }\n\n    /*\n        These next few helper functions address the issue that getValues() returns an array of arrays\n        In each case we need the zero column (there won't be any others)\n    */\n    function getColumnValuesAsStrings(tbl: ExcelScript.Table, columnName: string): string[] {\n        //console.log(`Getting column strings from ` + tbl.getName() + `[${columnName}]`);\n        return tbl.getColumnByName(columnName).getRangeBetweenHeaderAndTotal().getValues().map(r => r[0].toString());\n    }\n\n    function getColumnValuesAsNumbers(tbl: ExcelScript.Table, columnName: string): number[] {\n        //console.log(`Getting column ints from ` + tbl.getName() + `[${columnName}]`);\n        return tbl.getColumnByName(columnName).getRangeBetweenHeaderAndTotal().getValues().map(r => Number(r[0]));\n    }\n\n    function getColumnValuesAsBooleans(tbl: ExcelScript.Table, columnName: string): boolean[] {\n        //console.log(`Getting column booleans from ` + tbl.getName() + `[${columnName}]`);\n        return tbl.getColumnByName(columnName).getRangeBetweenHeaderAndTotal().getValues().map(r => Boolean(r[0]));\n    }\n\n    function getSelectedDefaultUnit(unitName: string): string {\n        switch (unitName.toLowerCase()) {\n            case `area`:\n            case `unit area`:\n            case `with unit area`:\n                return astrWorkbookSettingValues[3];\n\n            case `volwood`:\n            case `unit volwood`:\n            case `with unit volwood`:\n                return astrWorkbookSettingValues[4];\n\n            case `volntfp`:\n            case `unit volntfp`:\n            case `with unit volntfp`:\n                return astrWorkbookSettingValues[5];\n\n            case `volpesticide`:\n            case `unit volpesticide`:\n            case `with unit volpesticide`:\n                return astrWorkbookSettingValues[6];\n        }\n        // didn't recognise unit name\n        return ``;\n    }\n\n    function getDefaultFormula(unitName: string, questionIndex: string): string {\n        switch (unitName.toLowerCase()) {\n            case `area`:\n            case `unit area`:\n            case `with unit area`:\n                return `=` + astrWorkbookSettingNames[3];\n\n            case `volwood`:\n            case `unit volwood`:\n            case `with unit volwood`:\n                return `=` + astrWorkbookSettingNames[4];\n\n            case `volntfp`:\n            case `unit volntfp`:\n            case `with unit volntfp`:\n                return `=` + astrWorkbookSettingNames[5];\n\n            case `volpesticide`:\n            case `unit volpesticide`:\n            case `with unit volpesticide`:\n                return `=` + astrWorkbookSettingNames[6];\n        }\n\n        // didn't recognise unit name, check instead for other formulae\n        let intDfRow = astrDefaultFormulaeIndexes.indexOf(questionIndex);\n        if (intDfRow > -1) {\n            return astrDefaultFormulaeDefinitions[intDfRow];\n        }\n\n        return ``;\n    }\n\n    function findNamedItem(name: string, description: string): ExcelScript.NamedItem {\n        var ni: ExcelScript.NamedItem;\n        ni = workbook.getNamedItem(name);\n        if (!ni)\n            throw new Error(`Could not find ` + description);\n        else\n            return ni;\n    }\n\n    function findNamedItemValueString(name: string, defaultValue: string): string {\n        var ni: ExcelScript.NamedItem;\n        var strVal: string;\n        ni = workbook.getNamedItem(name);\n        if (!ni)\n            return defaultValue;\n        else {\n            strVal = ni.getRange().getValue().toString();\n            if (strVal == ``) strVal = defaultValue;\n            return strVal;\n        }\n    }\n\n    function findNamedItemValueNumber(name: string, defaultValue: number): number {\n        var ni: ExcelScript.NamedItem;\n        ni = workbook.getNamedItem(name);\n        if (!ni)\n            return defaultValue;\n        else\n            return Number(ni.getRange().getValue()).valueOf();\n    }\n\n    function isLocked(rng: ExcelScript.Range): boolean { return rng.getFormat().getProtection().getLocked(); }\n\n    function logProcessStatus(logMessage: string) {\n        rngNextProcessMessageCell.setValue(`+` + ((Date.now() - intStartTimeMS) / 1000) + `s ` + logMessage);\n        // shift down to next cell\n        rngNextProcessMessageCell = rngNextProcessMessageCell.getOffsetRange(1, 0);\n    }\n\n    function logToConsole(logMessage: string, verbosityLevel: number = 0) {\n        if (verbosityLevel <= intConsoleLoggingVerbosityLevel) {\n            if (verbosityLevel <= 1)\n                console.log(`+` + ((Date.now() - intStartTimeMS) / 1000) + `s ` + logMessage);\n            else\n                //logProcessStatus(logMessage);\n                console.log(logMessage);\n        }\n    }\n\n    function setExitMessage(exitMessage: string) { workbook.getNamedItem(`txRefExitStatusMessage`).getRange().getCell(0, 0).setValue(exitMessage); }\n\n    function exitWithError(errorMessage: string) {\n        setExitMessage(errorMessage);\n        intSheetNo = 9999;   // forces sheet mapping loop to exit\n        if (!booDebugMode) resetWorkingData();\n        return;\n    }\n\n    function resetWorkingData() {\n        try {\n            workbook.getNamedItem(`txRefListSheetNum`).getRange().setValue(0);\n            workbook.getNamedItem(`txWorkbookSettings`).getRange().clear(ExcelScript.ClearApplyTo.contents);\n            workbook.getNamedItem(`txSourceMatrixDataPresentCol`).setFormula(`=txRefUnmappedSourceRangeMarker`);\n            workbook.getNamedItem(`txOutputMatrixDataPresentCol`).setFormula(`=txRefUnmappedSourceRangeMarker`);\n\n            wsTxData.getRange().clear(ExcelScript.ClearApplyTo.all);\n            wsTxData.getRange().setNumberFormatLocal(\"General\");\n\n            if (booResetMode)\n                workbook.getApplication().setCalculationMode(ExcelScript.CalculationMode.automatic);\n            else\n                workbook.getApplication().setCalculationMode(calcModeAtStart);\n\n            // Remove all the linked workbook references - can only run on the web (!?!)\n            /*\n            let externalWorkbooks = workbook.getLinkedWorkbooks() as ExcelScript.LinkedWorkbook[];\n            if ( externalWorkbooks.length > 0 )\n            {\n                logProcessStatus(`Breaking links to ${externalWorkbooks.length} other workbooks in the transferred data`);\n\n                // Remove all the links to those workbooks.\n                // This changes the value of cells with workbook links to \"#CONNECT!\".\n                externalWorkbooks.forEach((workbookLink) => { workbookLink.breakLinks(); });\n            }\n            */\n        }\n        catch (exc) { logToConsole(`Error resetting working data: ${exc.code} on line ${exc.line} ${exc.type} ${exc.method} : ${exc.message}`, 0); }\n        return;\n    }\n\n    function unprotectSheet(sheetName: string) {\n        let ws: ExcelScript.Worksheet = workbook.getWorksheet(sheetName);\n        if (ws) {\n            let wsp: ExcelScript.WorksheetProtection = ws.getProtection();\n            if (wsp.getProtected()) {\n                try { wsp.unprotect(strUnlockPassword); }\n                catch (exc) {\n                    console.log(`Suspected bad password for unlocking sheet ${strUnlockPassword}`);\n                    console.log(exc);\n                    throw new Error(`Password not accepted when trying to unlock sheet ${sheetName}`);\n                }\n            }\n        }\n        else\n            throw new Error(`Could not find sheet ${sheetName} to unlock`);\n    }\n\n    /**\n     * This functions' workings copied from the Release DAR Template script.\n     */\n    function reprotectSheet(sheetNum: number) {\n        let ws: ExcelScript.Worksheet = workbook.getWorksheet(astrSheetNames[sheetNum]);\n        if (ws) {\n            let wsp: ExcelScript.WorksheetProtection = ws.getProtection();\n            // do not attempt to lock if sheet is already locked\n            if (!wsp.getProtected()) {\n                let objProtectionOptions: ExcelScript.WorksheetProtectionOptions =\n                {\n                    allowAutoFilter: true,\n                    allowDeleteColumns: false,\n                    allowDeleteRows: false,\n                    allowEditObjects: false,\n                    allowEditScenarios: false,\n                    allowFormatCells: false,\n                    allowFormatColumns: abooSheetProtectionAllowRowInsertion[sheetNum],\n                    allowFormatRows: true,\n                    allowInsertColumns: false,\n                    allowInsertHyperlinks: true,\n                    allowInsertRows: abooSheetProtectionAllowColFormatting[sheetNum],\n                    allowPivotTables: false,\n                    allowSort: true,\n                    selectionMode: ExcelScript.ProtectionSelectionMode.normal\n                }\n                wsp.protect(objProtectionOptions, strUnlockPassword);\n            }\n        }\n        else throw new Error(`Could not find sheet ${astrSheetNames[sheetNum]} to relock`);\n    }\n\n}\n\n","description":"","parameterInfo":"{\"version\":1,\"originalParameterOrder\":[],\"parameterSchema\":{\"type\":\"object\",\"default\":{},\"x-ms-visibility\":\"internal\"},\"returnSchema\":{\"type\":\"object\",\"properties\":{}},\"signature\":{\"comment\":\"\",\"parameters\":[{\"name\":\"workbook\",\"comment\":\"\"}]}}","apiInfo":"{\"variant\":\"synchronous\",\"variantVersion\":2}"}