简体   繁体   中英

Split (break apart) merged table cells in google spreadsheet with an apps script

When operating (copying/moving) ranges containing merged cells, I always get the error “Your paste overlaps with merged cells. Please unmerge the cells and try again”. But when trying to unmerge the cells in the range using Range#breakApart , I get another error: “The selected cells cannot be merged.”, which is even more confusing since I'm not trying to merge anything, I'm only trying to break the cells apart.

Turns out breakApart only works when the range it is called on encompasses all merged cells , not just parts of the merge area.

This would not be too bad. The problem starts once you realize that there is no way in the API to get the ranges of merged cells. So the best way to solve this (that I've found so far) is just to gently extend your range until no error occurs:

var breakRange = myRange;
for(;;) {
  try {
    breakRange.breakApart();
    break;
  } catch(e) {
    breakRange = mySheet.getRange(
      breakRange.getRowIndex(),
      breakRange.getColumnIndex(),
      Math.min(
        breakRange.getHeight()+5,
        mySheet.getMaxRows()-breakRange.getRowIndex()+1
      ),
      Math.min(
        breakRange.getWidth()+5,
        mySheet.getMaxColumns()-breakRange.getColumnIndex()+1
      )
    );
  }
}

(I've added five rows/cols instead of one since getting ranges seems to be a rather expensive process). This works quite well so long as you don't have additional merged cells being added as you expand your search area.

When it comes to merged cells in spreadsheets, the Google Apps Scripts API is broken fundamentally in at least the following ways:

  1. There is no way to get an array of ranges of merged cells in a sheet/range.
  2. There should be a way to see which cells have been split ( breakApart only returns the original range “for chaining”). This makes it impossible to split merges and then have each cell contain the content of the original merge cell.
  3. Trying to use breakApart in a range that encompasses only a part of a merge cell should still break apart the entire merge instead of throwing an exception.

All in all, the API as it currently stands only works for spreadsheets whose layout and usage the script author herself has complete control over.

This function breaks apart the range of merged cells that partially intersects with the provided cell or range. It uses Sheets advanced service to identify all merged ranges in a given sheet, as suggested by Adam Morris ( @clssrmtechtools ) in this thread .

Demo spreadsheet here .

/** 
* Breaks apart merged cells in range
* @param {sheet} sheet Sheet of range
* @param {range} rangeToSplit Cell or cells that intersect with whole merged range
*/
function breakApartAux(sheet, rangeToSplit) {

  // Current spreadsheet & sheet
  let ss = SpreadsheetApp.getActiveSpreadsheet();
  let activeSSId = ss.getId();
  let activeSId = sheet.getSheetId();

  // Get sheet's merges using advanced sheets service
  let merges = Sheets.Spreadsheets.get(activeSSId).sheets.find(s => s.properties.sheetId == activeSId).merges;

  if (merges == undefined) SpreadsheetApp.getUi().alert('No merged cells found in sheet.');
  else {

    // Logger.log(merges);

    // Cells to merge R/C
    let rowS = rangeToSplit.getRow();
    let rowE = rowS + rangeToSplit.getNumRows() - 1;
    let colS = rangeToSplit.getColumn();
    let colE = colS + rangeToSplit.getNumColumns() - 1;

    // Find overlapping merged range
    // Advanced service merged ranges start in 0 and are right-open [..)
    let merge = merges.find(m => {
                            let mRowS = m.startRowIndex + 1;
                            let mRowE = m.endRowIndex;
                            let mColS = m.startColumnIndex + 1;
                            let mColE = m.endColumnIndex;

                            // Check for overlapping
                            return ((rowS >= mRowS && rowS <= mRowE)  || 
                                    (rowE <= mRowE && rowE >= mRowS)  ||
                                    (rowS < mRowS && rowE > mRowE)) &&
                                   ((colS >= mColS && colS <= mColE)  ||
                                    (colE <= mColE && colE >= mColS)  ||
                                    (colS < mColS && colE > mColE));
    })

    // Overlapping range?
    if (merge == undefined) SpreadsheetApp.getUi().alert('No merged cells found in specified range.');
    else {
      // Break apart whole range
      ss.getActiveSheet().getRange(merge.startRowIndex + 1,
                                   merge.startColumnIndex + 1,
                                   merge.endRowIndex - merge.startRowIndex,
                                   merge.endColumnIndex - merge.startColumnIndex).breakApart();
    }
  }

}

Inspired by Raphael's answer, this is how to unmerge all cells in a sheet. Here's what I used for data imports that merged cells that had adjacent null values, ie I was unmerging cell a1 with cell b1 where a1 had a value and b1 did not:

function unmergeSheet() {

var mySheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
var myRange = mySheet.getDataRange()
var breakRange = myRange;
breakRange.breakApart();

}

Here's the simplest way I've found:

function unmerge() {
  var sheetName = 'Sheet1';   // Change!
  var a1Notation = 'A1:B15';  // Change!  
  var range = SpreadsheetApp.getActive().getSheetByName(sheetName).getRange(a1Notation);  
  var mergedRanges = range.getMergedRanges();
  for (var i = 0; i < mergedRanges.length; i++)
  {
    mergedRanges[i].breakApart();
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM