如何防止Google表单将表单输入转换为科学计数格式

How to prevent Google Forms from converting form input to scientific notation format

我有一个简单的脚本设置,它根据 Google 表单条目发送电子邮件,使用基于脚本的 VLookup 获取联系人电子邮件。在某些情况下,Google 表单会将输入到表单字段中的较长数字转换为科学记数法。我一直在使用的一种解决方法是在数字前输入一个撇号——出于某种原因,这会使单元格格式保持为纯文本。我想找到一个不需要这个额外步骤的解决方案。

sheet 的表单只有一个字段 eGCs。 eGCs 字段可以包含字母和数字的任意组合,并且可以是多行字符串。该脚本向 onFormSubmit 用户发送一封电子邮件,电子邮件正文中包含 eGCs 字段条目。当我尝试提交一个很长的字符串,它只是数字并且表单输入变量被转换为科学记数法时,问题就出现了。

我需要用户在 eGCs 字段中输入的内容与他们在响应 1 sheet 和发送的电子邮件正文中输入的内容完全相同。这是代码:

function onFormSubmit(e) {

   var eGCs = e.values[1];
   var email = Session.getActiveUser().getEmail();

//Replace the Google Sheets formatted line breaks with HTML line breaks so they display properly in the email:
   eGCs = eGCs.replace(/\n/g, '<br>');

//Send the email:   
   var subject = "This is only a test";
   var body = eGCs;
   MailApp.sendEmail(email, subject, body, {htmlBody: body})
   return
   }

如果我提交

6110523527643880 

...进入表格,数字将更改为科学记数法,并在 sheet 和发送的电子邮件中显示为 6.11052E+15。如果我提交多行字符串,例如:

6110523527643880
6110523527643880
6110523527643880

...然后脚本工作正常并且表单字段条目未转换(可能是因为 Google 不再将其视为数字)。无论表单条目是单行还是多行,我都需要它完全按照输入显示。

这是我的example sheet / script / form。应该是public,欢迎大家测试

我假设 eGCs 是带有数字的响应。

e.values[2] 将始终以字符串形式返回(在本例中为“6.15312E+16”),因此您无法将其转换为原始数字,因为您丢失了最后一个 2 之后的所有内容。即使你转换它,你能得到的最好的是“61531200000000000”

相反,您可以从电子表格中提取值。 在 onFormSubmit() 函数的开头添加此代码:

 var ss = SpreadsheetApp.getActiveSpreadsheet();
 var sheet = ss.getSheetByName("Form Responses 1");
 var eGCs = sheet.getRange(sheet.getLastRow(), 2, 1, 1).getValue(); 
 try {
   eGCs = eGCs.toFixed();
 } catch (e) {
   Logger.log(eGCs);
 }

如果是数字,您的 eGCs 现在将 return 作为完整数字,否则它将 return 作为文本。

如果您希望电子表格在提交新回复时具有正确的格式,请将此添加到您的代码中:

sheet.getRange(sheet.getLastRow(), 2, 1, 1).setNumberFormat("000");

这会将表单添加的新行转换为正确的格式。这不会影响电子表格中的实际值,只会影响其格式化方式。

表单响应 在表单(相对于电子表格)中将响应存储为字符串。您的触发函数可以获取表单的响应以获取响应者输入的字符串。

function onFormSubmit(e) {
  // Get response sheet. First version works only in contained script,
  // second works even in stand-alone scripts.
//  var sheet = SpreadsheetApp.getActiveSheet();
  var sheet = e.range.getSheet();

  // Get URL of associated form & open it
  var formUrl = sheet.getParent().getFormUrl();
  var form = FormApp.openByUrl(formUrl);

  // Get response matching the timestamp in this event
  var timestamp = new Date(e.namedValues.Timestamp);
  // NOTE: There is a race condition between the updates in Forms and Sheets.
  // Sometimes (often!) the Spreadsheet Form Submission trigger function is invoked
  // before the Forms database has completed persisting the new Responses. As
  // a result, we might get no results when asking for the most recent response. 
  // To work around that, we will wait and try again.
  var timeToGiveUp = 0;
  do {
    if (timeToGiveUp > 0) Utilities.sleep(1000); // sleep 1s on subsequent tries
    timeToGiveUp++;
    var responses = form.getResponses(timestamp);
  } while (responses.length == 0 && (timeToGiveUp < 3));
  Logger.log("time to give up "+timeToGiveUp);
  var response = responses[0]; // assume just one response matches timestamp
  var itemResponses = response.getItemResponses();
  var eGCsItemNumber = 1;  // Indicates where the question appears in the form
  var eGCs = itemResponses[eGCsItemNumber-1].getResponse().toString();

  // You now have exactly what the respondent typed, as a string.
  // It can be used as-is in an email, for example.
  var body = "The user entered: "+eGCs;
  MailApp.sendEmail(
     Session.getActiveUser().getEmail(),
     "This is only a test",
     body
   );

  // To preserve the value in the spreadsheet, we must
  // force it to remain a string by prepending a tick (')
  var eGCsCol = 2;
  e.range.offset(0,eGCsCol-1,1,1).setValue("'"+eGCs);
}

注意 wrt 竞争条件 评论:代码的这个区域需要的实际工作是一行:

var responses = form.getResponses(timestamp);

在玩这个的时候,我发现我经常收到一个异常,就像这个答案下面的评论中提到的一样...

Cannot find method getResponses(object)

事实证明,这只发生在函数被表单提交事件触发时,而不是当 运行 来自 editor/debugger 和模拟事件时。这意味着,在短时间内,我们尝试处理的响应不会通过调用 getResponses() 返回。

由于共享文档的实施方式,任何更改都存在 传播延迟...这是在资产的一个视图中进行更改所需的时间传播到所有其他视图。

在这种情况下,我们的触发器函数已启动电子表格事件,然后打开表单视图并尝试读取 该视图包含它们之前的最新响应。

一个简单的解决方法是 sleep() 一段时间,以便完成传播。

Utilities.sleep(5000);   // 5s pause
var responses = form.getResponses(timestamp);

简单,是的 - 但效率低下,因为即使我们不需要等待,我们也会等待。第二个问题是确定多长时间才足够……如果明天改变了怎么办?

仅当第一次不成功时,所选的解决方法才会重试获取响应。它只会在重试时等待。而且它不会永远等待 - 通过 timeToGiveUp 应用了 限制条件 。 (我们可以在循环后添加额外的成功检查,但是如果我们超过了时间限制,下一条语句将通过异常处理,我们可以让它完成脏工作。)

var timeToGiveUp = 0;
do {
  if (timeToGiveUp > 0) Utilities.sleep(1000); // sleep 1s on subsequent tries
  timeToGiveUp++;
  var responses = form.getResponses(timestamp);
} while (responses.length == 0 && (timeToGiveUp < 3));

不止一行代码,但更健壮。