13
votes

In a Google docs spreadsheet. If cells A1 & A2 are merged, is there a way to confirm they are merged, using google apps script?

There is a merge function in GAS https://developers.google.com/apps-script/class_range#merge

But there is no function or example that shows how to check if cells are merged.

getValues etc just returns an empty string for the cell. e.g. this does not work.

function testMerge() {

  var spreadsheet = SpreadsheetApp.openById('Z3ppTjxNUE........'); 
  var sheet = spreadsheet.getSheets()[0];
  var range = sheet.getRange("A3:A4");
  var values = range.getValues();
  var formulas = range.getFormulasR1C1();
  var formulasA1 = range.getFormulas();

  range = sheet.getRange("A4");
  range.setValue("a");

  range = sheet.getRange("A3:A4");
  var values2 = range.getValues();
  var formulas2 = range.getFormulasR1C1();
  var formulasA12 = range.getFormulas();


  var count = range.getHeight();


}
9

9 Answers

6
votes

range.isPartOfMerge()

It seems the .isPartOfMerge() method was implemented (11 sept 2016).

So you can check if a range is merged as such:

function testMerge() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName("Sheet1");
  var range = sheet.getRange(1,1,2); // example for cells A1 and A2
  Logger.log(range.isPartOfMerge());
}

Docs https://developers.google.com/apps-script/reference/spreadsheet/range#ispartofmerge


OLD APPROACH

Posting here another approach that I like more than the accepted answer.

If you try to use GAS to store a value in a cell of a merged range, except the first cell, it will not store it.

As such, we can exploit this and attempt to store the value and check if it was stored, using a function similar to this one:

  function isMerged(range){
  try {
    range.getDataSourceUrl()
  } catch (err) {
    throw "isMerged function only works with valid GAS Range objects"
  }
  var sheet = range.getSheet();
  var rangeData = range.getValues();

  var testValue = "testing merge";

  if (rangeData.length == 1 && rangeData[0].length == 1) return false; // we have a single cell. Can't possible be merged :)

  if (rangeData.length == 1) {
    // We have a single row. Which means we're testing a row with multiple columns
    var mainCell=range.getA1Notation().split(":")[0];
    var rowNumber= sheet.getRange(mainCell).getRow();
    var nextColNumber = sheet.getRange(mainCell).getColumn()+1;
    var row = rangeData[0];
    var oldValue = row[1]; // for testing purposes, we're chosing the next possible column

    sheet.getRange(rowNumber,nextColNumber).setValue(testValue);
    if (sheet.getRange(rowNumber,nextColNumber).getValue() !== testValue) {
      return true;
    } else {
      sheet.getRange(rowNumber,nextColNumber).setValue(oldValue);
      return false;
    };
  } else if (rangeData[0].length == 1) {
    // We have multiple rows and a single column.
    var mainCell=range.getA1Notation().split(":")[0];
    var nextRowNumber= sheet.getRange(mainCell).getRow()+1;
    var colNumber = sheet.getRange(mainCell).getColumn();
    var oldValue = rangeData[1][0]; // for testing purposes, we're chosing the next possible row

    sheet.getRange(nextRowNumber,colNumber).setValue(testValue);
    if (sheet.getRange(nextRowNumber,colNumber).getValue() !== testValue) {
      return true;
    } else {
      sheet.getRange(nextRowNumber,colNumber).setValue(oldValue);
      return false;
    };
  } else {
    // We have multiple rows and multiple columns
    var mainCell=range.getA1Notation().split(":")[0];
    var nextRowNumber= sheet.getRange(mainCell).getRow()+1;
    var nextColNumber = sheet.getRange(mainCell).getColumn()+1;
    var oldValue = rangeData[1][1]; // for testing purposes, we're chosing the next possible row and next possible column

    sheet.getRange(nextRowNumber,nextColNumber).setValue(testValue);
    if (sheet.getRange(nextRowNumber,nextColNumber).getValue() !== testValue) {
      return true;
    } else {
      sheet.getRange(nextRowNumber,nextColNumber).setValue(oldValue);
      return false;
    };
  }

  // if none of these checks worked, something's fishy. Either way, return false
  return false
}

I've ran a series of quick tests and it returned true/false accordingly, but there is a limitation that I didn't have time to cover: If you had a merged range like "A1:F1" and you checked the range "A1:G1" (so, one more column), it will return true, even if G1 is not part of the merged range - because it checks only the next column using as reference the first bound row/column.

5
votes

Yes, now you can use isPartOfMerge to check.

And for anyone wants to get the value of merged cell:

var value = (cell.isPartOfMerge() ? cell.getMergedRanges()[0].getCell(1,1) : cell).getValue();

Hope it help.

3
votes

For those like me who didn't know the .breakapart() method to unmerge cells: https://developers.google.com/apps-script/class_range#breakApart

Thanks to Henrique for the tip !

2
votes

It's not possible to know that. There's already an issue opened regarding this, but it is quite old and has no activity. You should star it (to keep track of updates and kind of vote for it) and leave a comment to see if you can get some attention.

But what I do when I need to assure merged cells are as I want them is to break everything apart and merge again, just to be sure. Of course, this is not a solution, just an idea that might suit you as well.

1
votes

If anyone here needs a work around for this problem, I wrote a little tool that takes most of the donkey work out of making a key value file of all merged cells from exported HTML.

You can find it here: https://github.com/martinhbramwell/CellMergeWorkAround

I wrote it because I have a small project that depends on being able to fully clone ranges from one spreadsheet to another. Obviously, I have added my vote to wanting the issue fixed.

(Note: I would have added this as a comment to the correct answer, but lack the necessary points.)

1
votes

There seems to be a workaround exploiting the fact that merged cells always return a white background. The code below appears to work for me; I will appreciate it if anyone can confirm.

function testMerged() {
  var WHITE = '#ffffff';
  var NON_WHITE = '#fffffe';
  var range = SpreadsheetApp.getActiveSheet().getDataRange();
  range.setBackground(NON_WHITE);
  range.getBackgrounds().forEach(function (row, rowNum) {
    row.forEach(function (col, colNum) {
      if (col === WHITE) { Logger.log('Cell merged at row ' + rowNum + ' col ' + colNum); }
    });
  });
  range.setBackground(WHITE);  
}
0
votes

Download as HTML: You can download a sheet as HTML, this will include the merged cell info. Not a nice solution, but it works.

Example download url:

docs.google.com/feeds/download/spreadsheets/Export?key=* spreadsheetkey *&exportFor‌​mat=html&gid=3

where gid is the sheet gid number. - signin is required via google drive api (see google drive DrEdit Tutorial for details)

0
votes

Found this solution, posted here:
https://code.google.com/p/google-apps-script-issues/issues/detail?id=618

But only works for active range

// This is my function to test the active range is merged or not and return
// true or false by passing active range to it
function ismerge() {
  var spread = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spread.getActiveSheet();
  var last_row = sheet.getActiveRange().getLastRow();
  var last_col = sheet.getActiveRange().getLastColumn();

  Logger.log("Last row: %s", last_row);
  var active_row = sheet.getActiveRange().getRow();
  var active_col = sheet.getActiveRange().getColumn();
  Logger.log("Row: %s", active_row);
  if ( (last_row == active_row) && (last_col == active_col) ) {
    Logger.log("Cell not merged");
    return false;
  }
  else {
    Logger.log("Cell merged");
    return true;
  }
}
0
votes

Maybe it's a bit late answer since I didn't see this problem for new version of google spreadsheets. But, I still use old spreadsheets version for my project. So I was needed somehow figure it out.

I need to copy format of previous column for new columns. This is part of code below:

      // trying to find first column with week day (white background)
      var background = '';
      for(var columnIndex = 3; columnIndex<=columnLetters.length; columnIndex++){
        background = sheet.getRange(1, columnIndex).getBackground();
        if(background == 'white' || background == ''){
          tmpRange = columnLetters[columnIndex]+':'+columnLetters[columnIndex];
          Logger.log('tmpRange: '+tmpRange);
          // workaround to be able to copy format if there is some merged cells
          try{
            Logger.log('try');
            // copy format of previous column
            sheet.getRange(tmpRange).copyTo(
              sheet.getRange('B1'), {formatOnly:true}
            );
          }catch(e) {
            Logger.log('continue');
            continue;
          }
          Logger.log('break');
          break;
        }
      }

So, the most important part here is:

try{
  Logger.log('try');
  // copy format of previous column
  sheet.getRange(tmpRange).copyTo(
    sheet.getRange('B1'), {formatOnly:true}
  );
}catch(e) {
  Logger.log('continue');
  continue;
}

If it's impossible to copy those cells it just continue to search others.