Apps Script Utilities.parseCsv假定双引号

时间:2016-04-16 00:22:35

标签: google-apps-script

使用Utilities.parseCsv()时,双引号内的换行符被假定为新行。此函数的输出数组将有几个不正确的行。

我该如何解决这个问题,或解决它?

编辑:具体来说,我可以逃避仅在双引号内存在的换行符吗?即

/r/n "I have some stuff to do:/r/n Go home/r/n Take a Nap"/r/n

将被转移到:

/r/n "I have some stuff to do://r//n Go home//r//n Take a Nap"/r/n

Edit2:2012年的错误报告:https://code.google.com/p/google-apps-script-issues/issues/detail?id=1871

5 个答案:

答案 0 :(得分:2)

因此,我有一个较大的csv文件,大约10MB 5万行,其中每行的末尾都有一个字段,用户输入的注释中包含各种字符。当我测试一小部分行时,我发现提议的正则表达式解决方案正在工作,但是当我将大文件扔给它时,又出现了一个错误,在尝试了正则表达式的一些操作之后,我什至崩溃了整个运行时间

顺便说一句,我正在V8运行时上运行代码。

head了大约一个小时之后,并收到了来自AppsSript运行时的错误消息。我有个主意,如果某些奇怪的用户决定以某种奇怪的方式使用反斜杠使某些转义出错,那该怎么办? 因此,我尝试将数据中的所有反斜杠替换为其他内容一段时间,直到获得parseCsv()返回的数组。 有效! 我的假设是,在行尾使用\会破坏替换项。

所以我的最终解决方案是:

function testParse() {
    let csv =
        '"title1","title2","title3"\r\n' +
        '1,"person1","A ""comment"" with a \\ and \\\r\n a second line"\r\n' +
        '2,"person2","Another comment"';

    let sanitizedString =
        csv.replace(/\\/g, '::back-slash::')
            .replace(/(?=["'])(?:"[^"\\]*(?:\\[\s\S][^"\\]*)*"|'[^'\\]\r?\n(?:\\[\s\S][^'\\]\r?\n)*')/g,
                match => match.replace(/\r?\n/g, "::newline::"));
    let arr = Utilities.parseCsv(sanitizedString);
    for (let i = 0, rows = arr.length; i < rows; i++) {
        for (let j = 0, cols = arr[i].length; j < cols; j++) {
            arr[i][j] = 
                arr[i][j].replace(/::back-slash::/g,'\\')
                    .replace(/::newline::/g,'\r\n');

        }
    }
    Logger.log(arr)
}

输出:

[20-02-18 11:29:03:980 CST] [[title1, title2, title3], [1, person1, A "comment" with a \ and \
 a second line], [2, person2, Another comment]]

答案 1 :(得分:1)

我遇到了同样的问题,终于弄明白了。感谢道格拉斯的正则表达式/代码(我必须说,有点超过我的头脑)它与相关领域很好地匹配。不幸的是,这只是战斗的一半。显示的替换将简单地用\r\n替换整个字段。因此,仅当CSV文件中""之间的任何内容仅为\r\n时才有效。如果它与其他数据一起嵌入到字段中,它会默默地销毁该数据。要解决另一半问题,您需要使用函数作为替换。 replace将匹配字段作为参数,因此您可以在函数中执行简单的替换调用以仅处理该字段。实施例...

数据:

"Student","Officer

RD

Special Member","Member",705,"2016-07-25 22:40:04 EDT"

要处理的代码:

var dataString = myBlob().getDataAsString(); 
var escapedString = dataString.replace(/(?=["'])(?:"[^"\](?:\[\s\S][^"\])"|'[^'\]\r\n(?:\[\s\S][^'\]\r\n)')/g, function(match) { return match.replace(/\r\n/g,"\r\n")} ); 
var csvData = Utilities.parseCsv(escapedString);

现在单独评估"Officer\r\nRD\r\nSpecial Member"字段,以便替换函数中的match.replace调用可以非常简单直接。

答案 2 :(得分:0)

从另一个帖子的另一个回复中检索并稍微修改了一个正则表达式:https://stackoverflow.com/a/29452781/3547347

正则表达式:(?=["'])(?:"[^"\\]*(?:\\[\s\S][^"\\]*)*"|'[^'\\]\r\n(?:\\[\s\S][^'\\]\r\n)*')

代码:

  var dataString = myBlob.getDataAsString();
  var escapedString = dataString.replace(/(?=["'])(?:"[^"\\]*(?:\\[\s\S][^"\\]*)*"|'[^'\\]\r\n(?:\\[\s\S][^'\\]\r\n)*')/g, '\\r\\n');

答案 3 :(得分:0)

为避免尝试理解正则表达式,我在下面找到了一种解决方法,而不使用Utilities.parseCsv()。我正在逐行复制数据。

这是怎么回事:

如果您可以找到一种在CSV末尾添加额外列的方法,该列始终包含完全相同的 value ,则可以根据以下内容强制使用特定的“换行符”到该

然后,将整行复制到A列中,并使用Google应用脚本的专用 splitTextToColumns()方法...

在下面的示例中,我从HTML表单获取CSV。之所以可行,是因为我还具有对用户从中获取CSV的数据库的管理员访问权限,因此我可以在所有CSV文件中强制使用最后一列...

function updateSheet(form) {
  var fileData = form.myFile;
  // gets value from form
  blob = fileData.getBlob();
  var name = String(form.folderId);
  // gets value from form

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.setActiveSheet(ss.getSheetByName(name), true);

  sheet.clearContents().clearFormats();

  var values = [];
  // below, the "Dronix" value is the value that I could force at the end of each row
  var rows = blob.contents.split('"Dronix",\n');

  if (rows.length > 1) {
    for (var r = 2, max_r = rows.length; r < max_r; ++r) {
      sheet.getRange(r + 6, 1, 1, 1).setValue(String(rows[r]));
    }
  }

  var spreadsheet = SpreadsheetApp.getActive();
  spreadsheet.getRange("A:A").activate();
  spreadsheet.getRange("A:A").splitTextToColumns();
}

答案 4 :(得分:0)

使用Sheets API对您可能会有所帮助。 就我而言,它可以正常工作,而无需替换包含双引号多行文本的CSV文本。

首先,您需要确保以下内容:

启用高级服务

要使用高级Google服务,请按照以下说明进行操作:

  1. 在脚本编辑器中,选择资源>高级Google服务...
  2. 在出现的高级Google服务对话框中, 点击要使用的服务旁边的 on / off 开关。
  3. 在对话框中单击确定

如果可以,您可以使用以下方式将CSV文本数据导入工作表:

var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('some_name');
const resource = {
    requests: [
        {
            pasteData: {
                data: csvText, // Your CSV data string
                coordinate: {sheetId: sheet.getSheetId()},
                delimiter: ",",
            }
        }

    ]
};
Sheets.Spreadsheets.batchUpdate(resource, ss.getId());

或用于TypeScript,clasp可以使用:

var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('some_name');
const resource: GoogleAppsScript.Sheets.Schema.BatchUpdateSpreadsheetRequest = {
    requests: [
        {
            pasteData: {
                data: csvText, // Your CSV data string
                coordinate: {sheetId: sheet.getSheetId()},
                delimiter: ",",
            }
        }

    ]
};
Sheets.Spreadsheets.batchUpdate(resource, ss.getId());