diff --git a/poi/src/main/java/org/apache/poi/ss/usermodel/RangeCopier.java b/poi/src/main/java/org/apache/poi/ss/usermodel/RangeCopier.java
index c6b82a6860..97aaa94e79 100644
--- a/poi/src/main/java/org/apache/poi/ss/usermodel/RangeCopier.java
+++ b/poi/src/main/java/org/apache/poi/ss/usermodel/RangeCopier.java
@@ -54,9 +54,11 @@ public abstract class RangeCopier {
}
/** Uses input pattern to tile destination region, overwriting existing content. Works in following manner :
- * 1.Start from top-left of destination.
- * 2.Paste source but only inside of destination borders.
- * 3.If there is space left on right or bottom side of copy, process it as in step 2.
+ *
+ * - Start from top-left of destination.
+ * - Paste source but only inside of destination borders.
+ * - If there is space left on right or bottom side of copy, process it as in step 2.
+ *
* @param tilePatternRange source range which should be copied in tiled manner
* @param tileDestRange destination range, which should be overridden
* @param copyStyles whether to copy the cell styles
@@ -90,7 +92,12 @@ public abstract class RangeCopier {
} while (nextRowIndexToCopy <= tileDestRange.getLastRow());
if (copyMergedRanges) {
- sourceSheet.getMergedRegions().forEach((mergedRangeAddress) -> destSheet.addMergedRegion(mergedRangeAddress));
+ int rowOffset = tileDestRange.getFirstRow() - tilePatternRange.getFirstRow();
+ int columnOffset = tileDestRange.getFirstColumn() - tilePatternRange.getFirstColumn();
+ sourceSheet.getMergedRegions().stream()
+ .filter(tilePatternRange::contains)
+ .map(sourceMergedRegion -> sourceMergedRegion.shift(rowOffset, columnOffset))
+ .forEach(destSheet::addMergedRegion);
}
int tempCopyIndex = sourceSheet.getWorkbook().getSheetIndex(sourceCopy);
diff --git a/poi/src/main/java/org/apache/poi/ss/util/CellRangeAddress.java b/poi/src/main/java/org/apache/poi/ss/util/CellRangeAddress.java
index 91a60273a4..9cd1612d8f 100644
--- a/poi/src/main/java/org/apache/poi/ss/util/CellRangeAddress.java
+++ b/poi/src/main/java/org/apache/poi/ss/util/CellRangeAddress.java
@@ -133,4 +133,18 @@ public class CellRangeAddress extends CellRangeAddressBase {
}
return new CellRangeAddress(a.getRow(), b.getRow(), a.getCol(), b.getCol());
}
+
+ /**
+ * Shifts cell range by specified number of rows and columns.
+ *
+ * @param rows rows to shift by.
+ * @param columns columns to shift by.
+ * @return copy of this {@link CellRangeAddress}, shifted by rows and columns.
+ * @since 6.0.0
+ */
+ public CellRangeAddress shift(int rows, int columns) {
+ return new CellRangeAddress(getFirstRow() + rows, getLastRow() + rows,
+ getFirstColumn() + columns, getLastColumn() + columns);
+ }
+
}
diff --git a/poi/src/main/java/org/apache/poi/ss/util/CellRangeAddressBase.java b/poi/src/main/java/org/apache/poi/ss/util/CellRangeAddressBase.java
index 7153dff29a..3b2e1e373c 100644
--- a/poi/src/main/java/org/apache/poi/ss/util/CellRangeAddressBase.java
+++ b/poi/src/main/java/org/apache/poi/ss/util/CellRangeAddressBase.java
@@ -228,6 +228,21 @@ public abstract class CellRangeAddressBase implements Iterable, Dup
other._firstCol <= this._lastCol;
}
+ /**
+ * Determines whether this CellRangeAddress fully contains CellRangeAddress.
+ *
+ * @param other a candidate cell range address to check if contained within this range
+ * @return returns true if this range contains other range.
+ * @see #isInRange(int, int) for checking if a single cell contains
+ * @since 6.0.0
+ */
+ public boolean contains(CellRangeAddressBase other) {
+ return this._firstRow <= other._firstRow &&
+ this._lastRow >= other._lastRow &&
+ this._firstCol <= other._firstCol &&
+ this._lastCol >= other._lastCol;
+ }
+
/**
* Useful for logic like table/range styling, where some elements apply based on relative position in a range.
* @return set of {@link CellPosition}s occupied by the given coordinates. Empty if the coordinates are not in the range, never null.
diff --git a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestRangeCopier.java b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestRangeCopier.java
index 6239eb22b5..5ba489385b 100644
--- a/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestRangeCopier.java
+++ b/poi/src/test/java/org/apache/poi/ss/usermodel/BaseTestRangeCopier.java
@@ -152,9 +152,27 @@ public abstract class BaseTestRangeCopier {
transSheetRangeCopier.copyRange(tileRange, tileRange, false, true);
assertEquals(cellContent, getCellContent(destSheet, "D6"));
assertFalse(destSheet.getMergedRegions().isEmpty());
- destSheet.getMergedRegions().forEach((mergedRegion) -> {
- assertEquals(mergedRangeAddress, mergedRegion);
- });
+ destSheet.getMergedRegions().forEach(mergedRegion ->
+ assertEquals(mergedRangeAddress, mergedRegion)
+ );
+ }
+
+ @Test
+ void testSameSheetMergedRanges() {
+ String cellContent = "D6 merged to E7";
+
+ // create cell merged from D6 to E7
+ CellRangeAddress mergedRangeAddress = new CellRangeAddress(5,6,3,4);
+ Cell cell = sheet1.createRow(5).createCell(3);
+ cell.setCellValue(cellContent);
+ sheet1.addMergedRegion(mergedRangeAddress);
+
+ CellRangeAddress tileRange = CellRangeAddress.valueOf("D6:E7");
+ CellRangeAddress targetRange = CellRangeAddress.valueOf("D8:E9");
+ rangeCopier.copyRange(tileRange, targetRange, false, true);
+ assertEquals(cellContent, getCellContent(sheet1, "D8"));
+ assertFalse(sheet1.getMergedRegions().isEmpty());
+ assertTrue(sheet1.getMergedRegions().stream().anyMatch(targetRange::equals));
}
protected static String getCellContent(Sheet sheet, String coordinates) {