3

我正在编写一个与 Google Form 的响应表交互的脚本。

FormApp.getActiveForm().getDestinationId()

给我电子表格ID,但我找不到获取表格本身的方法。用户可以更改其名称和位置,因此我需要获取其 id,例如

Sheet.getSheetId()

我还必须确定响应使用的列数。它不等于表格中的问题数量。我可以计算表格中的项目数:

Form.getItems().length

然后搜索 gridItems,将每个行数相加,然后将它们相加减一:

+ gridItem.getRows().length - 1

最后,我认为没有办法将每个问题与工作表中的每一列联系起来,而是通过以某种方式将列名称与项目标题进行比较。

谢谢

4

4 回答 4

6

@tehhowch 非常接近正确答案,但代码存在问题:不能保证form.getPublishedUrl()并且sheet.getFormUrl()会返回完全相同的字符串。在我的情况下,form.getPublishedUrl()返回一个形成为https://docs.google.com/forms/d/e/{id}/viewformsheet.getFormUrl()返回的 URL https://docs.google.com/forms/d/{id}/viewform。由于表单 id 是 URL 的一部分,因此更健壮的实现将是:

function get_form_destination_sheet(form) {
    const form_id = form.getId();
    const destination_id = form.getDestinationId();
    if (destination_id) {
        const spreadsheet = SpreadsheetApp.openById(destination_id);
        const matches = spreadsheet.getSheets().filter(function (sheet) {
            const url = sheet.getFormUrl();
            return url && url.indexOf(form_id) > -1;
        });
        return matches.length > 0 ? matches[0] : null; 
    }
    return null;
}
于 2019-08-20T03:53:05.053 回答
5

现在有一种方法可以验证具有多个链接表格的 Google 表格文件中的哪个表格对应于当前表格 - 通过使用2017Sheet#getFormUrl()年添加到Sheet该类中。

function getFormResponseSheet_(wkbkId, formUrl) {
  const matches = SpreadsheetApp.openById(wkbkId).getSheets().filter(
    function (sheet) {
      return sheet.getFormUrl() === formUrl;
    });
  return matches[0]; // a `Sheet` or `undefined`
}
function foo() {
  const form = FormApp.getActiveForm();
  const destSheet = getFormResponseSheet_(form.getDestinationId(), form.getPublishedUrl());
  if (!destSheet)
    throw new Error("No sheets in destination with form url '" + form.getPublishedUrl() + "'");
  // do stuff with the linked response destination sheet.
}

如果您取消了表单和目标电子表格的链接,那么显然您将无法使用getDestinationIdor getFormUrl

于 2018-07-23T17:17:52.213 回答
1

我也需要这个,而且很明显仍然没有应用程序脚本方法来促进它。最后,我着手寻找一种可靠的方法来确定工作表 id,这就是我通过编程解决方法得到的结果:

  1. 添加一个临时表单项,其标题是随机字符串(或类似的合适的)
  2. 等待新的对应列添加到目标工作表(通常需要几秒钟)
  3. 查看目标中的每个工作表,直到在标题行中找到这个新的表单项标题字符串
  4. 删除添加的临时表单项
  5. 等待工作表中的相应列与表单取消链接并变为可删除(通常需要几秒钟)
  6. 删除临时表单项对应的列
  7. 返回工作表 ID

我敢肯定有些人不会喜欢这种方法,因为它会修改表单和电子表格,但它确实运作良好。

包括必要的等待时间,执行所有查找/清理操作大约需要 12 秒。

这是我的这种方法的代码,以防其他人喜欢使用它。

// Takes Apps Script 'Form' object as single paramater
// The second parameter 'obj', is for recursion (do not pass a second parameter)
// Return value is either: 
// - null (if the form is not linked to any spreadsheet)
// - sheetId [int]
// An error is thrown if the operations are taking too long

function getFormDestinationSheetId(form, obj) {

  var obj = obj || {}; // Initialise object to be passed between recursions of this function

  obj.attempts = (obj.attempts || 1);

  Logger.log('Attempt #' + obj.attempts);

  if (obj.attempts > 14) {
    throw 'Unable to determine destination sheet id, too many failed attempts, taking too long. Sorry!';
  }

  obj.spreadsheetId = obj.spreadsheetId || form.getDestinationId();

  if (!obj.spreadsheetId) {
    return null; // This means there actually is no spreadsheet destination set at all.

  } else {
    var tempFormItemTitle = '### IF YOU SEE THIS, PLEASE IGNORE! ###';

    if (!obj.tempFormItemId && !obj.sheetId) { // If the sheet id exists from a previous recusion, we're just in a clean up phase
      // Check that temp item does not already exist in form
      form.getItems(FormApp.ItemType.TEXT).map(function(textItem) {
        var textItemTitle = textItem.getTitle();
        Logger.log('Checking against form text item: ' + textItemTitle);
        if (textItemTitle === tempFormItemTitle) {
          obj.tempFormItemId = textItem.getId();
          Logger.log('Found matching form text item reusing item id: ' + obj.tempFormItemId);
        }
        return 0;
      }); // Note: Just using map as handy iterator, don't need to assign the output to anything

      if (!obj.tempFormItemId) {
        Logger.log('Adding temporary item to form');
        obj.tempFormItemId = form.addTextItem().setTitle(tempFormItemTitle).getId();
      }
    }

    obj.spreadsheet = obj.spreadsheet || SpreadsheetApp.openById(obj.spreadsheetId);
    obj.sheets = obj.sheets || obj.spreadsheet.getSheets();
    obj.sheetId = obj.sheetId || null;

    var sheetHeaderRow = null;

    for (var i = 0, x = obj.sheets.length; i < x; i++) {
      sheetHeaderRow = obj.sheets[i].getSheetValues(1, 1, 1, -1)[0];

      for (var j = 0, y = sheetHeaderRow.length; j < y; j++) {
        if (sheetHeaderRow[j] === tempFormItemTitle) {
          obj.sheetId = obj.sheets[i].getSheetId();
          Logger.log('Temporary item title found in header row of sheet id: ' + obj.sheetId);
          break;
        }
      }
      if (obj.sheetId) break;
    }

    // Time to start cleaning things up a bit!
    if (obj.sheetId) {

      if (obj.tempFormItemId) {
        try {
          form.deleteItem(form.getItemById(obj.tempFormItemId));
          obj.tempFormItemId = null;
          Logger.log('Successfully deleted temporary form item');
        } catch (e) {
          Logger.log('Tried to delete temporary form item, but it seems it was already deleted');
        }
      }

      if (obj.sheetId && !obj.tempFormItemId && !obj.tempColumnDeleted) {
        try {
          obj.sheets[i].deleteColumn(j + 1);
          obj.tempColumnDeleted = true;
          Logger.log('Successfully deleted temporary column');
        } catch (e) {
          Logger.log('Could not delete temporary column as it was still attached to the form');
        }
      }

      if (!obj.tempFormItemId && obj.tempColumnDeleted) {
        Logger.log('Completed!');
        return obj.sheetId;
      }
    }

    SpreadsheetApp.flush(); // Just in case this helps!

    // Normally this process takes three passes, and a delay of 4.5 secs seems to make it work in only 3 passes most of the time
    // Perhaps if many people are submitting forms/editing the spreadsheet, this delay would not be long enough, I don't know.
    obj.delay = ((obj.delay || 4500));

    // If this point is reached then we're not quite finished, so try again after a little delay
    Logger.log('Delay before trying again: ' + obj.delay / 1000 + ' secs');
    Utilities.sleep(obj.delay);
    obj.attempts++;

    return getFormDestinationSheetId(form, obj);
  }
}

于 2017-07-09T03:10:54.540 回答
0

要获取电子表格,一旦您拥有 DestinationID,请使用SpreadsheetApp.openById()。一旦你有了它,你就可以检索一个工作表数组,并通过索引获取响应表,而不管它的名称是什么。

var destId = FormApp.getActiveForm().getDestinationId();
var ss = SpreadsheetApp.openById(destId);
var respSheet = ss.getSheets()[0];  // Forms typically go into sheet 0.
...

从这一点开始,您可以使用其他电子表格服务方法来操作电子表格中的数据。

我还必须确定响应使用的列数。它不等于表格中的问题数量。我可以计算表格中的项目数......(但这与电子表格不匹配)

你是对的 - 当前项目的数量不等于电子表格中的列数。每个响应在目标工作表中占据的列数包括已从表单中删除的任何问题,并且不包括不是问题的项目。此外,电子表格中列的顺序是创建问题的顺序 - 当您重新排列表单或插入新问题时,电子表格的列顺序不会反映新顺序。

假设电子表格中的唯一列来自表单,以下是您可以使用它们的方法:

...
var data = respSheet.getDataRange().getValues(); // 2d array of form responses
var headers =  data[0];  // timestamp and all questions
var numColumns = headers.length;  // count headers
var numResponses = data.length - 1; // count responses

最后一点是正确的,您需要关联名称。

最后,我认为没有办法将每个问题与工作表中的每一列联系起来,而是通过以某种方式将列名称与项目标题进行比较。

于 2013-05-27T15:34:33.953 回答