【问题标题】:For Loop Google Apps Script? VBA for ExampleFor Loop Google Apps 脚本?以 VBA 为例
【发布时间】:2020-01-31 18:59:33
【问题描述】:

我在 Excel 中有一个 VBA UDF,用于计算父子计算的顶级数量。在这里问过类似的问题Offset in JavaScript UDF? VBA for Example

代码是

Function ParentChildCalc(critCell As Range, valCell As Range) As Double
    Application.Volatile
    Dim numYes As Long
    Dim i As Long
    Dim temp As Double
    Do Until i = 5000
        If critCell.Offset(i, 0) = "Yes" Then numYes = numYes + 1
        If numYes = 2 Then Exit Do
        temp = temp + valCell.Offset(i, 0)
        i = i + 1
    Loop
    ParentChildCalc = temp
End Function

我正在尝试将此 UDF 转换为 JavaScript 或 TypeScript,但我被公式的偏移和循环部分卡住了。

这是我最好的镜头。

/**
 * ParentChildCalc1
 * @customfunction ParentChildCalc1
 * @param {string} critCell Critical Cell
 * @param {number} valCell Value Cell
 * @returns {number} ParentChildCalc1
 */
function ParentChildCalc1(critCell, valCell) {
    var blank = ""
    var TopYes = "Yes"
    var i = 1
    var numYes = 1
    if (critCell[i,0] == TopYes) {
        numYes = numYes++
            if numYes = 2 {
            }
        return (valCell);
    } else {
        return (blank);
    }
}

【问题讨论】:

  • 我认为这是一个关于如何在 JavaScript/TypeScript 中创建 for 循环的基本问题,该循环一直循环到 i 达到 5000。您应该为 JavaScript 标记它,并可能删除 excel 的标记和office-js。
  • 如果您为不了解 VBA 的人描述循环中究竟发生了什么,这看起来很容易解决。直觉上看起来几乎很清楚,但并不完全。

标签: javascript for-loop google-sheets user-defined-functions


【解决方案1】:

在 Google 表格中运行脚本的工作方式与 Excel 不同,后者可能有好有坏。拥有 Excel VBA 背景,您必须转变思维方式。

  1. 在 Excel 中编写用户定义函数 (UDF) 时,您可以像这样传递 Range 参数:=MyUDF(A1,C3)。函数签名将为您提供两个 Range 对象作为参数。在 Google 表格中,情况并非如此。所有范围参数都转换为值(或值数组)。这意味着,如果您在表格单元格中键入=MyUDF(A1,C3,您的脚本代码将获得 A1 的值和 C3 的值。这不是你想要的。

基于this answer,您最好的选择是解析原始单元格公式并提取范围参数。然后,您可以为您的 UDF 创建一个 Range 对象。这是一套迂回的逻辑,但与 Sheets 实现底层函数和脚本的方式是一致的。以下函数将从活动单元格中提取公式并对其进行解析,从而为您提供预期的范围对象。

注意:如果 UDF 调用位于复合公式中,您必须修改此函数以挑选出您的 UDF 调用。例如,如果您的 公式为=MyUDF(A1,C3)。但是如果你的公式是 不工作 =If(A2='Yes','',MyUDF(A1,C3))。可以修改函数来检测 这个,但这里没有提供。

function GetParamRange(paramNumber) {
  var activeSheet = SpreadsheetApp.getActiveSheet();
  var formula = SpreadsheetApp.getActiveRange().getFormula();

  //--- expecting two parameters, split here and check
  const MAXARGS = 2;
  var args = formula.match(/=\w+\((.*)\)/i)[1].split(',');
  if (args.length != MAXARGS) {
    throw new Error('in GetParamRange: ' + args.join(',') + '- invalid number of arguments, expecting ' + MAXARGS);
  }

  if ((paramNumber < 1) || (paramNumber > MAXARGS)) {
    throw new Error('in GetParamRange: invalid parameter requested, must be ' + MAXARGS + ' or less');
  }

  //--- is the worksheet specified?
  var paramRange;
  var parts = args[paramNumber-1].split('!');
  if (parts.length == 2) {
    //--- use worksheet and cell to get the range
    Logger.log('activeSheet = ' + activeSheet.getName());
    ss = SpreadsheetApp.getActive().getSheetByName(parts[0].replace(/\'/g, ''));
    paramRange = ss.getRange(parts[1]);
  }
  else {
    //--- get the range from the active sheet
    paramRange = activeSheet.getRange(parts[0]);
  }
  return(paramRange);
}
  1. 在我列出实际函数之前,您必须了解编码 UDF 的一个重要方面。在 Excel 中,您可以将 UDF 显式标记为 Volatile。在 Google 表格中,这是不可能的。老实说,我使用 Google 表格的工作还不够多,无法了解所有详细信息,因此您必须四处搜索。
  2. 用户定义函数本身接受两个参数,但这些参数实际上是通过调用GetParamRange 创建的。从那里,这是一个简单的循环来添加您的运行总和(带有额外的安全限制)。

如果超过安全限制会抛出错误。

 /**
 * ParentChildCalc1
 * @customfunction ParentChildCalc1
 * @param {string} critCell Critical Cell 
 * @param {number} valCell Value Cell 
 * @returns {number} ParentChildCalc1
 */
function ParentChildCalc1(critCell, valCell) {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var critCell = GetParamRange(1);
  var valCell = GetParamRange(2);

  const safetyNet = 5000;
  var runningSum = 0;
  var numYes = 0;
  for (var i = 0; !valCell.offset(i,0).isBlank(); i++) {
    if (critCell.offset(i,0).getValue() == 'Yes') {
      numYes++;
      if (numYes == 2) break;
      if (i == safetyNet) {
        throw new Error('exceeded safety net limit in ParentChildCalc1, searching ' + safetyNet + ' rows.');
      }
    }
    runningSum = runningSum + valCell.offset(i,0).getValue();
  }
  return (runningSum);
}

这是单个块中的整个代码:

 /**
 * ParentChildCalc1
 * @customfunction ParentChildCalc1
 * @param {string} critCell Critical Cell 
 * @param {number} valCell Value Cell 
 * @returns {number} ParentChildCalc1
 */
function ParentChildCalc1(critCell, valCell) {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var critCell = GetParamRange(1);
  var valCell = GetParamRange(2);

  const safetyNet = 5000;
  var runningSum = 0;
  var numYes = 0;
  for (var i = 0; !valCell.offset(i,0).isBlank(); i++) {
    if (critCell.offset(i,0).getValue() == 'Yes') {
      numYes++;
      if (numYes == 2) break;
      if (i == safetyNet) {
        throw new Error('exceeded safety net limit in ParentChildCalc1, searching ' + safetyNet + ' rows.');
      }
    }
    runningSum = runningSum + valCell.offset(i,0).getValue();
  }
  return (runningSum);
}

function GetParamRange(paramNumber) {
  var activeSheet = SpreadsheetApp.getActiveSheet();
  var formula = SpreadsheetApp.getActiveRange().getFormula();

  //--- expecting two parameters, split here and check
  const MAXARGS = 2;
  var args = formula.match(/=\w+\((.*)\)/i)[1].split(',');
  if (args.length != MAXARGS) {
    throw new Error('in GetParamRange: ' + args.join(',') + '- invalid number of arguments, expecting ' + MAXARGS);
  }

  if ((paramNumber < 1) || (paramNumber > MAXARGS)) {
    throw new Error('in GetParamRange: invalid parameter requested, must be ' + MAXARGS + ' or less');
  }

  //--- is the worksheet specified?
  var paramRange;
  var parts = args[paramNumber-1].split('!');
  if (parts.length == 2) {
    //--- use worksheet and cell to get the range
    Logger.log('activeSheet = ' + activeSheet.getName());
    ss = SpreadsheetApp.getActive().getSheetByName(parts[0].replace(/\'/g, ''));
    paramRange = ss.getRange(parts[1]);
  }
  else {
    //--- get the range from the active sheet
    paramRange = activeSheet.getRange(parts[0]);
  }
  return(paramRange);
}

因此,要使用此功能,只需将公式 =ParentChildCalc1(D7,C7) 放入单元格 E7 中即可。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-24
    • 2018-05-26
    相关资源
    最近更新 更多