如何在 Google 电子表格之间有效复制边框(包含格式和值)?

问题描述 投票:0回答:1

问题描述

在 Google Apps 脚本中,当我们需要将同一电子表格中的一张工作表的范围复制到另一张工作表时,可以使用

copyTo
方法。但是,当尝试将范围复制到不同电子表格中的工作表时,即使使用 Sheets API,也没有可用的直接方法。

之前的答案提供了一种解决方法,用于将大多数格式属性(边框除外)从一个电子表格复制到另一个电子表格。它涉及检索格式化信息并将其应用到目标范围。不幸的是,此方法不处理边框格式。

要将整个工作表复制到另一个电子表格并保留边框,可以对整个工作表使用

copyTo
方法,然后覆盖值。但是,如果目标工作表上的其他内容应保持不变,则这种方法是不切实际的。

Tanaike 在 Stack Overflow 上的回复中提供了处理边框格式的有效解决方案。通过利用 Sheets API,可以检索边框格式详细信息。然而,将这些格式详细信息应用到另一个电子表格中的范围仍然是一个挑战。

我正在寻求有关如何最有效地将使用 Sheets API 检索到的边框格式应用于另一个电子表格中的范围的指导。我对其他方法持开放态度。

相关链接

google-sheets google-apps-script google-sheets-api spreadsheet
1个回答
0
投票
  • 您想要将边框从某个电子表格的工作表范围复制到另一个电子表格的工作表范围
    • 我使用 Tanaike 给出的脚本通过 Sheets API 获取源边框
    • 我共享了两个电子表格来尝试代码
    • I have verified it for a range of 1000 rows and 26 columns

脚本1:

这是根据田池的答案制作的

function getBordersEfficiently_usingSheetsAPI_thanksTo_Tanaike(spreadsheetId, sheetName, rangeA1){

  // Ref: https://stackoverflow.com/questions/56055708/what-is-the-most-efficient-way-to-get-borders-in-google-apps-script

  // link to open: https://docs.google.com/spreadsheets/d/1c1WLAZahyjfXoM3MQKz4S5HgDlmgLEHlvrC4Rk09i28/
  // var spreadsheetId = "1c1WLAZahyjfXoM3MQKz4S5HgDlmgLEHlvrC4Rk09i28"; 
  // var sheetName = "Sheet1"; // Name of the source sheet
  // var rangeA1 = "A1:E10"; 

  var token = ScriptApp.getOAuthToken();
  var fields = "sheets/data/rowData/values/userEnteredFormat/borders";
  var params = {
      method: "get",
      headers: {Authorization: "Bearer " +  token},
      muteHttpExceptions: true,
  };
  // var rangeA1WithSheetName = sheetName + "!A1:Z1000";
  var rangeA1WithSheetName = sheetName + "!" + rangeA1;
  var url = "https://sheets.googleapis.com/v4/spreadsheets/" + spreadsheetId + "?ranges=" + encodeURIComponent(rangeA1WithSheetName) + "&fields=" + encodeURIComponent(fields); 
  var res = UrlFetchApp.fetch(url, params);
  var result = JSON.parse(res.getContentText());
  // Logger.log("result = %s", result);
  return result;      
  
};

结果:

  • 脚本 1 非常省时

响应示例:

result = {sheets=[{data=[{rowData=[{values=[{userEnteredFormat={borders={left={colorStyle={rgbColor={}}, style=SOLID_MEDIUM, color={}, width=2.0}, top={colorStyle={rgbColor={}}, style=SOLID_MEDIUM, color={}, width=2.0}, bottom={color={}, style=SOLID, colorStyle={rgbColor={}}, width=1.0}, right={color={}, width=1.0, style=SOLID, colorStyle={rgbColor={}}}}}}, {userEnteredFormat={borders={bottom={colorStyle={rgbColor={}}, style=SOLID, width=1.0, color={}}, left={width=1.0, style=SOLID, color={}, colorStyle={rgbColor={}}}, right={color={}, width=2.0, colorStyle={rgbColor={}}, style=SOLID_MEDIUM}, top={width=2.0, style=SOLID_MEDIUM, color={}, colorStyle={rgbColor={}}}}}}]}, {values=[{userEnteredFormat={borders={top={style=SOLID, colorStyle={rgbColor={}}, color={}, width=1.0}, bottom={colorStyle={rgbColor={}}, width=2.0, color={}, style=SOLID_MEDIUM}, right={style=SOLID, color={}, colorStyle={rgbColor={}}, width=1.0}, left={width=2.0, color={}, style=SOLID_MEDIUM, colorStyle={rgbColor={}}}}}}, {userEnteredFormat={borders={bottom={colorStyle={rgbColor={}}, style=SOLID_MEDIUM, width=2.0, color={}}, right={style=SOLID_MEDIUM, width=2.0, colorStyle={rgbColor={}}, color={}}, left={color={}, style=SOLID, width=1.0, colorStyle={rgbColor={}}}, top={colorStyle={rgbColor={}}, style=SOLID, color={}, width=1.0}}}}]}]}]}]}

脚本2:

在此示例脚本中,使用上述脚本 1 检索边框,然后处理接收到的对象以转换为“updateBorders”方法的另一个对象。

function copyPasteBordersOnly(source_spreadsheetId, source_sheetName, source_RangeA1, desination_spreadsheetId, desination_sheetName, desination_cellA1){

  const bordersObj = getBordersEfficiently_usingSheetsAPI_thanksTo_Tanaike(source_spreadsheetId, source_sheetName, source_RangeA1);
  Logger.log("bordersObj = %s", bordersObj);
  var source_Range = SpreadsheetApp.openById(source_spreadsheetId).getSheetByName(source_sheetName).getRange(source_RangeA1);

  const numRows_source_Range = source_Range.getNumRows();
  const numCols_source_Range = source_Range.getNumColumns();

  // If the range is out of bound, doesn't exist in the sheet then the received reponse is
  // bordersObj = {error={message=Range ('Sheet1'!AA89:AB92) exceeds grid limits. Max rows: 1000, max columns: 26, status=INVALID_ARGUMENT, code=400.0}}
  if("error" in bordersObj){
    return Logger.log("error message = %s", bordersObj.error.message);
  }

  const rowData_ofBorders = bordersObj.sheets[0].data[0].rowData;
  Logger.log("rowData_ofBorders = %s", rowData_ofBorders);

  if(rowData_ofBorders == null){
    return Logger.log("There are no borders in the source range");
  }

  const noOfRows_inReceivedObj = rowData_ofBorders.length;
  // These might be different in some cells have no borders in your source range
  Logger.log("noOfRows in received obj = %s, and numRows_source_Range = %s", noOfRows_inReceivedObj, numRows_source_Range);
  

  var updateBorders_Objs_2DArr = [];

  rowData_ofBorders.forEach((r, i) => {
    const values_arr = r.values;
    // Logger.log("values_arr = %s", values_arr);
    
    updateBorders_Objs_2DArr.push([]);
    if(values_arr == null){
        // Object is empty, means no borders in the first row of source range
        // we have to add blank object for each cell in this row
        updateBorders_Objs_2DArr[i].push(...Array(numCols_source_Range).fill({}));
    }else{
      // No. of columns in received object depends on the columns (from last) in source range having borders
      // Logger.log("This is no of columns in received object, values_arr.length = %s", values_arr.length);
      values_arr.forEach((r2, j) => {
        if(Object.keys(r2).length === 0){
          // Object is empty, means no borders
          updateBorders_Objs_2DArr[i].push({});
        }else{
          const userEntered_obj = r2.userEnteredFormat;
          // Logger.log("i = %s, j = %s, userEntered_obj = %s",i, j, userEntered_obj);
          const keys_userEntered_obj = Object.keys(userEntered_obj);
          // Logger.log("keys_userEntered_obj = %s", keys_userEntered_obj);
          keys_userEntered_obj.forEach(key =>{
            const borderObj_OfACell = {};
            const val_userEntered_obj = userEntered_obj[key];
            // Logger.log("val_userEntered_obj = %s", val_userEntered_obj);
            // ["top", "bottom", "left", "right"].forEach(side => {
            Object.keys(val_userEntered_obj).sort((a, b)=> a-b).forEach(side => {
              const thisSideProperties = val_userEntered_obj[side];
              // Logger.log("side = %s, thisSideProperties = %s", side, thisSideProperties);
              borderObj_OfACell[side] = {
                "width": thisSideProperties.width,
                "style": thisSideProperties.style,
                "color": ["red", "green", "blue"].reduce((clrObj, clr) => {
                  clrObj[clr] = clr in thisSideProperties.color ? thisSideProperties.color[clr] : 0.0;
                  return clrObj;
                  }, {}),
              }
            }); 
            // Logger.log("borderObj_OfACell = %s", borderObj_OfACell);
            updateBorders_Objs_2DArr[i].push(borderObj_OfACell);
          });
        }
      });
      // If the borders are not applied to all columns, or 
      // there are some cells in row (at the end of the row) which do not have any borders
      const numCols_InThisRow_OfReceivedObj = updateBorders_Objs_2DArr[i].length;
      // Logger.log("numCols_InThisRow_OfReceivedObj = %s", numCols_InThisRow_OfReceivedObj);
      if(updateBorders_Objs_2DArr[i].length < numCols_source_Range){
        // lets add extra blank objects
        updateBorders_Objs_2DArr[i].push(...Array(numCols_source_Range - numCols_InThisRow_OfReceivedObj).fill({}));
      }
    }
  })
  // Logger.log("updateBorders_Objs_2DArr = %s", updateBorders_Objs_2DArr);

  // Logger.log("numRows in received obj = %s", updateBorders_Objs_2DArr.length);
  // updateBorders_Objs_2DArr.forEach((r, i) => Logger.log("The length of '%s'th row = %s", i, r.length));
  // Logger.log("numCols in received obj = %s", updateBorders_Objs_2DArr[0].length);


  // ////////////////////////////////////////////////////////////////////////////////////////////
  // // I can't do the following
  // ////////////////////////////////////////////////////////////////////////////////////////////
  // // I was trying to do this, to decrease the size of the object to send
  // // If we can idenify the ranges having same innerHorizontal and innerVertical ranges
  // // then we can club those objects into one
  // const rowWiseGroup_sameFormatting = [];
  // const colWiseGroup_sameFormatting = [];

  // // Now let us verify, if same border is there for rows
  // for(var i = 0; i < updateBorders_Objs_2DArr.length; i++){
  //   // jo ek row na badha cell ma sarakhi borders hoy to check karie
  //   for(var j = 0; j < updateBorders_Objs_2DArr[i].length - 1; j++){
  //     const aCellStr = JSON.stringify(updateBorders_Objs_2DArr[i][j]);
  //     const nextCell_InRow_String = JSON.stringify(updateBorders_Objs_2DArr[i][j+1]);
  //     // Logger.log("aCellStr===nextCell_InRow_String = %s", aCellStr===nextCell_InRow_String);
  //     if(aCellStr===nextCell_InRow_String){
  //       Logger.log(`For i = ${i}, j = ${j}, aCellStr===nextCell_InRow_String`);
  //       rowWiseGroup_sameFormatting.push([i, j]);
  //     }
  //     const belowCell_InCol_String = i < updateBorders_Objs_2DArr.length - 1 ? JSON.stringify(updateBorders_Objs_2DArr[i+1][j]) : null;
  //     if(belowCell_InCol_String){
  //       if(aCellStr===belowCell_InCol_String){
  //         Logger.log(`For i = ${i}, j = ${j}, (aCellStr===belowCell_InCol_String`);
  //         colWiseGroup_sameFormatting.push([i, j]);
  //       }
  //     }
  //     Logger.log(`For i = ${i}, j = ${j}`);
  //     Logger.log("aCellStr = %s", aCellStr);
  //     Logger.log("nextCell_InRow_String = %s", nextCell_InRow_String);
  //     Logger.log("belowCell_InCol_String = %s", belowCell_InCol_String);
  //   }
  // }

  // Logger.log("rowWiseGroup_sameFormatting = %s", rowWiseGroup_sameFormatting);
  // Logger.log("colWiseGroup_sameFormatting = %s", colWiseGroup_sameFormatting);
  // /////////////////////////////////////////////////////////////////////////////////////////////

  const desination_sheet = SpreadsheetApp.openById(desination_spreadsheetId).getSheetByName(desination_sheetName);
  const desination_cell = desination_sheet.getRange(desination_cellA1);
  const destination_startRowIndex = desination_cell.getRow() - 1;
  const destination_startColumnIndex = desination_cell.getColumn() - 1;

  Logger.log("destination_startRowIndex = %s, destination_startColumnIndex = %s", destination_startRowIndex, destination_startColumnIndex);
  const desination_sheetId = desination_sheet.getSheetId();

  updateBorders_Objs_2DArr = updateBorders_Objs_2DArr.map((r, i) => {
    return r.map((obj, j) => {
      const anObj = {};
      
      anObj["updateBorders"] = {
        "range":{
          "sheetId": desination_sheetId,
          "startRowIndex": destination_startRowIndex + i,
          "endRowIndex": destination_startRowIndex + i + 1,
          "startColumnIndex": destination_startColumnIndex + j,
          "endColumnIndex": destination_startColumnIndex + j + 1,
        },
      };
      // left, right, bottom, top borders to add here
      Object.keys(obj).forEach(key => anObj["updateBorders"][key] = obj[key]);
      return anObj;
    });
  });

  // Here we may convert it into a 1D Array, however 2D Array also works fine

  // First to reset the borders, we have to set borders to none
  // so let us add first request object to reset borders
  const requestObj_toResetBorders =
    {
      updateBorders: {
        range: {
          sheetId: desination_sheetId,
          startRowIndex: destination_startRowIndex,
          endRowIndex: destination_startRowIndex + numRows_source_Range, // End row is exclusive, so add 1
          startColumnIndex: destination_startColumnIndex,
          endColumnIndex: destination_startColumnIndex + numCols_source_Range, // End column is exclusive, so add 1
        },
        top: {
          style: "none",
          width: 0.0,
          color: {
            red: 0.0, // Set red to 0 (assuming blue border as per your example)
            green: 0.0, // Set green to 0 (assuming blue border)
            blue: 0.0, // Set blue to desired color value
          },
        },
        bottom: {
          style: "none",
          width: 0.0,
          color: {
            red: 0.0, // Set red to 0 (assuming blue border as per your example)
            green: 0.0, // Set green to 0 (assuming blue border)
            blue: 0.0, // Set blue to desired color value
          },
        },
        left: {
          style: "none",
          width: 0.0,
          color: {
            red: 0.0, // Set red to 0 (assuming blue border as per your example)
            green: 0.0, // Set green to 0 (assuming blue border)
            blue: 0.0, // Set blue to desired color value
          },
        },
        right: {
          style: "none",
          width: 0.0,
          color: {
            red: 0.0, // Set red to 0 (assuming blue border as per your example)
            green: 0.0, // Set green to 0 (assuming blue border)
            blue: 0.0, // Set blue to desired color value
          },
        },
        innerHorizontal: {
          style: "none",
          width: 0.0,
          color: {
            red: 0.0, // Set red to 0 (assuming blue border as per your example)
            green: 0.0, // Set green to 0 (assuming blue border)
            blue: 0.0, // Set blue to desired color value
          },
        },
        innerVertical: {
          style: "none",
          width: 0.0,
          color: {
            red: 0.0, // Set red to 0 (assuming blue border as per your example)
            green: 0.0, // Set green to 0 (assuming blue border)
            blue: 0.0, // Set blue to desired color value
          },
        },
      },
    };
  
  updateBorders_Objs_2DArr.unshift(requestObj_toResetBorders);

  // Logger.log("Last, updateBorders_Objs_2DArr = %s", updateBorders_Objs_2DArr);
  // Logger.log("Last, updateBorders_Objs_2DArr[0][0] = %s", updateBorders_Objs_2DArr[0][0]);

  const finalRequestJsonObj = {
    "requests": updateBorders_Objs_2DArr
  }

  // console.log("finalRequestJsonObj = ");
  // console.log(finalRequestJsonObj);
  // Logger.log(JSON.stringify(finalRequestJsonObj, null, 2)); // logs properly

  // Docs.Documents.batchUpdate(requests, spreadsheetId);
  const response = Sheets.Spreadsheets.batchUpdate(finalRequestJsonObj, desination_spreadsheetId);
  Logger.log(response);
  return response;
}

脚本3:

示例用例:

function tryBordersCopyPaste(){
  // Source for Batch Update shared with everyone,  all have access to this spreadsheet
  // link to open: https://docs.google.com/spreadsheets/d/1c1WLAZahyjfXoM3MQKz4S5HgDlmgLEHlvrC4Rk09i28/
  var source_spreadsheetId = "1c1WLAZahyjfXoM3MQKz4S5HgDlmgLEHlvrC4Rk09i28"; 
  var source_sheetName = "Sheet1"; // Name of the source sheet
  var source_RangeA1 = "B3:D6";
  // var source_RangeA1 = "F3:I7";
  // var source_RangeA1 = "G3:I7";
  // var source_RangeA1 = "A1:F10"; 
  // var source_RangeA1 = "A1:Z1000"; 

  // Destination for Batch Update shared with everyone, all can access this spreadsheet
  // link of destination sheet : https://docs.google.com/spreadsheets/d/19Z0urdlXd3AM6Xhk1JbIxPHRfl0fh2UA4ArJ-aP8X9E/
  var desination_spreadsheetId = "19Z0urdlXd3AM6Xhk1JbIxPHRfl0fh2UA4ArJ-aP8X9E"; 
  // Name of the destination sheet
  var desination_sheetName = "Sheet1"; 
  // first cell (left-top) of the destination range, numRows and numColumns should be as per the source range
  var desination_cellA1 = "A1"; 

  copyPasteBordersOnly(source_spreadsheetId, source_sheetName, source_RangeA1, desination_spreadsheetId, desination_sheetName, desination_cellA1);

}

回应:

response = {replies=[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}], spreadsheetId=19Z0urdlXd3AM6Xhk1JbIxPHRfl0fh2UA4ArJ-aP8X9E}

注:

  • 脚本 2
    not
    时间效率高,正如我在代码中提到的,我们将减小请求对象的大小(有效负载的大小)。为此,我们必须确定具有相同的内部水平和内部垂直边界的范围。然后我们可以将所有这些单元格的对象组合成整个范围的一个对象。如果有人对此感兴趣,我会很高兴。

备注:

  • 受Sheets API调用配额限制,不时变更。

参考资料:

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.