bug 58443: prevent corrupted workbooks by checking for overlapping merged regions before adding a merged region to a sheet; fix unit tests that produced corrupt workbooks; remove deprecated HSSFSheet#addMergedRegion(Region)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1711586 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
b40a0b01bb
commit
043a86e243
@ -673,22 +673,13 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
|
|||||||
_sheet.setGridsPrinted(value);
|
_sheet.setGridsPrinted(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated (Aug-2008) use <tt>CellRangeAddress</tt> instead of <tt>Region</tt>
|
|
||||||
*/
|
|
||||||
public int addMergedRegion(org.apache.poi.ss.util.Region region) {
|
|
||||||
return _sheet.addMergedRegion(region.getRowFrom(),
|
|
||||||
region.getColumnFrom(),
|
|
||||||
//(short) region.getRowTo(),
|
|
||||||
region.getRowTo(),
|
|
||||||
region.getColumnTo());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* adds a merged region of cells (hence those cells form one)
|
* adds a merged region of cells (hence those cells form one)
|
||||||
*
|
*
|
||||||
* @param region (rowfrom/colfrom-rowto/colto) to merge
|
* @param region (rowfrom/colfrom-rowto/colto) to merge
|
||||||
* @return index of this region
|
* @return index of this region
|
||||||
|
* @throws IllegalStateException if region intersects with an existing merged region
|
||||||
|
* or multi-cell array formula on this sheet
|
||||||
*/
|
*/
|
||||||
public int addMergedRegion(CellRangeAddress region) {
|
public int addMergedRegion(CellRangeAddress region) {
|
||||||
region.validate(SpreadsheetVersion.EXCEL97);
|
region.validate(SpreadsheetVersion.EXCEL97);
|
||||||
@ -697,6 +688,10 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
|
|||||||
// a multi-cell array formula defined in this sheet
|
// a multi-cell array formula defined in this sheet
|
||||||
validateArrayFormulas(region);
|
validateArrayFormulas(region);
|
||||||
|
|
||||||
|
// Throw IllegalStateException if the argument CellRangeAddress intersects with
|
||||||
|
// a merged region already in this sheet
|
||||||
|
validateMergedRegions(region);
|
||||||
|
|
||||||
return _sheet.addMergedRegion(region.getFirstRow(),
|
return _sheet.addMergedRegion(region.getFirstRow(),
|
||||||
region.getFirstColumn(),
|
region.getFirstColumn(),
|
||||||
region.getLastRow(),
|
region.getLastRow(),
|
||||||
@ -731,6 +726,15 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateMergedRegions(CellRangeAddress candidateRegion) {
|
||||||
|
for (final CellRangeAddress existingRegion : getMergedRegions()) {
|
||||||
|
if (existingRegion.intersects(candidateRegion)) {
|
||||||
|
throw new IllegalStateException("Cannot add merged region " + candidateRegion.formatAsString() +
|
||||||
|
" to sheet because it overlaps with an existing merged region (" + existingRegion.formatAsString() + ").");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control if Excel should be asked to recalculate all formulas on this sheet
|
* Control if Excel should be asked to recalculate all formulas on this sheet
|
||||||
* when the workbook is opened.
|
* when the workbook is opened.
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package org.apache.poi.ss.util;
|
package org.apache.poi.ss.util;
|
||||||
|
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
|
||||||
import org.apache.poi.ss.SpreadsheetVersion;
|
import org.apache.poi.ss.SpreadsheetVersion;
|
||||||
|
|
||||||
|
|
||||||
@ -124,6 +126,44 @@ public abstract class CellRangeAddressBase {
|
|||||||
_firstCol <= colInd && colInd <= _lastCol;
|
_firstCol <= colInd && colInd <= _lastCol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether or not this CellRangeAddress and the specified CellRangeAddress intersect.
|
||||||
|
*
|
||||||
|
* @param other
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean intersects(CellRangeAddressBase other) {
|
||||||
|
// see java.awt.Rectangle.intersects
|
||||||
|
// http://stackoverflow.com/questions/13390333/two-rectangles-intersection
|
||||||
|
|
||||||
|
// TODO: Replace with an intersection code that doesn't rely on java.awt
|
||||||
|
return getRectangle().intersects(other.getRectangle());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Replace with an intersection code that doesn't rely on java.awt
|
||||||
|
// Don't let this temporary implementation detail leak outside of this class
|
||||||
|
private final Rectangle getRectangle() {
|
||||||
|
int firstRow, firstCol, lastRow, lastCol;
|
||||||
|
|
||||||
|
if (!isFullColumnRange()) {
|
||||||
|
firstRow = Math.min(_firstRow, _lastRow);
|
||||||
|
lastRow = Math.max(_firstRow, _lastRow);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
firstRow = 0;
|
||||||
|
lastRow = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
if (!isFullRowRange()) {
|
||||||
|
firstCol = Math.min(_firstCol, _lastCol);
|
||||||
|
lastCol = Math.max(_firstCol, _lastCol);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
firstCol = 0;
|
||||||
|
lastCol = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
return new Rectangle(firstRow, firstCol, lastRow-firstRow+1, lastCol-firstCol+1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param firstCol column number for the upper left hand corner
|
* @param firstCol column number for the upper left hand corner
|
||||||
*/
|
*/
|
||||||
|
@ -327,6 +327,8 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
|||||||
*
|
*
|
||||||
* @param region (rowfrom/colfrom-rowto/colto) to merge
|
* @param region (rowfrom/colfrom-rowto/colto) to merge
|
||||||
* @return index of this region
|
* @return index of this region
|
||||||
|
* @throws IllegalStateException if region intersects with a multi-cell array formula
|
||||||
|
* @throws IllegalStateException if region intersects with an existing region on this sheet
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int addMergedRegion(CellRangeAddress region) {
|
public int addMergedRegion(CellRangeAddress region) {
|
||||||
@ -336,12 +338,22 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
|||||||
// a multi-cell array formula defined in this sheet
|
// a multi-cell array formula defined in this sheet
|
||||||
validateArrayFormulas(region);
|
validateArrayFormulas(region);
|
||||||
|
|
||||||
|
// Throw IllegalStateException if the argument CellRangeAddress intersects with
|
||||||
|
// a merged region already in this sheet
|
||||||
|
validateMergedRegions(region);
|
||||||
|
|
||||||
CTMergeCells ctMergeCells = worksheet.isSetMergeCells() ? worksheet.getMergeCells() : worksheet.addNewMergeCells();
|
CTMergeCells ctMergeCells = worksheet.isSetMergeCells() ? worksheet.getMergeCells() : worksheet.addNewMergeCells();
|
||||||
CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();
|
CTMergeCell ctMergeCell = ctMergeCells.addNewMergeCell();
|
||||||
ctMergeCell.setRef(region.formatAsString());
|
ctMergeCell.setRef(region.formatAsString());
|
||||||
return ctMergeCells.sizeOfMergeCellArray();
|
return ctMergeCells.sizeOfMergeCellArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that the candidate region does not intersect with an existing multi-cell array formula in this sheet
|
||||||
|
*
|
||||||
|
* @param region
|
||||||
|
* @throws IllegalStateException if candidate region intersects an existing array formula in this sheet
|
||||||
|
*/
|
||||||
private void validateArrayFormulas(CellRangeAddress region){
|
private void validateArrayFormulas(CellRangeAddress region){
|
||||||
int firstRow = region.getFirstRow();
|
int firstRow = region.getFirstRow();
|
||||||
int firstColumn = region.getFirstColumn();
|
int firstColumn = region.getFirstColumn();
|
||||||
@ -369,6 +381,20 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Verify that candidate region does not intersect with an existing merged region in this sheet
|
||||||
|
*
|
||||||
|
* @param candidateRegion
|
||||||
|
* @throws IllegalStateException if candidate region intersects an existing merged region in this sheet
|
||||||
|
*/
|
||||||
|
private void validateMergedRegions(CellRangeAddress candidateRegion) {
|
||||||
|
for (final CellRangeAddress existingRegion : getMergedRegions()) {
|
||||||
|
if (existingRegion.intersects(candidateRegion)) {
|
||||||
|
throw new IllegalStateException("Cannot add merged region " + candidateRegion.formatAsString() +
|
||||||
|
" to sheet because it overlaps with an existing merged region (" + existingRegion.formatAsString() + ").");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts the column width to fit the contents.
|
* Adjusts the column width to fit the contents.
|
||||||
|
@ -143,7 +143,7 @@ public abstract class BaseTestBugzillaIssues {
|
|||||||
Sheet template = wb.getSheetAt(0);
|
Sheet template = wb.getSheetAt(0);
|
||||||
|
|
||||||
template.addMergedRegion(new CellRangeAddress(0, 1, 0, 2));
|
template.addMergedRegion(new CellRangeAddress(0, 1, 0, 2));
|
||||||
template.addMergedRegion(new CellRangeAddress(1, 2, 0, 2));
|
template.addMergedRegion(new CellRangeAddress(2, 3, 0, 2));
|
||||||
|
|
||||||
Sheet clone = wb.cloneSheet(0);
|
Sheet clone = wb.cloneSheet(0);
|
||||||
int originalMerged = template.getNumMergedRegions();
|
int originalMerged = template.getNumMergedRegions();
|
||||||
|
@ -258,6 +258,46 @@ public abstract class BaseTestSheet {
|
|||||||
wb2.close();
|
wb2.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dissallow creating wholly or partially overlapping merged regions
|
||||||
|
* as this results in a corrupted workbook
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void addOverlappingMergedRegions() {
|
||||||
|
final Workbook wb = _testDataProvider.createWorkbook();
|
||||||
|
final Sheet sheet = wb.createSheet();
|
||||||
|
|
||||||
|
final CellRangeAddress baseRegion = new CellRangeAddress(0, 1, 0, 1);
|
||||||
|
sheet.addMergedRegion(baseRegion);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final CellRangeAddress duplicateRegion = new CellRangeAddress(0, 1, 0, 1);
|
||||||
|
sheet.addMergedRegion(duplicateRegion);
|
||||||
|
fail("Should not be able to add a merged region if sheet already contains the same merged region");
|
||||||
|
} catch (final IllegalStateException e) { } //expected
|
||||||
|
|
||||||
|
try {
|
||||||
|
final CellRangeAddress partiallyOverlappingRegion = new CellRangeAddress(1, 2, 1, 2);
|
||||||
|
sheet.addMergedRegion(partiallyOverlappingRegion);
|
||||||
|
fail("Should not be able to add a merged region if it partially overlaps with an existing merged region");
|
||||||
|
} catch (final IllegalStateException e) { } //expected
|
||||||
|
|
||||||
|
try {
|
||||||
|
final CellRangeAddress subsetRegion = new CellRangeAddress(0, 1, 0, 0);
|
||||||
|
sheet.addMergedRegion(subsetRegion);
|
||||||
|
fail("Should not be able to add a merged region if it is a formal subset of an existing merged region");
|
||||||
|
} catch (final IllegalStateException e) { } //expected
|
||||||
|
|
||||||
|
try {
|
||||||
|
final CellRangeAddress supersetRegion = new CellRangeAddress(0, 2, 0, 2);
|
||||||
|
sheet.addMergedRegion(supersetRegion);
|
||||||
|
fail("Should not be able to add a merged region if it is a formal superset of an existing merged region");
|
||||||
|
} catch (final IllegalStateException e) { } //expected
|
||||||
|
|
||||||
|
final CellRangeAddress disjointRegion = new CellRangeAddress(10, 11, 10, 11);
|
||||||
|
sheet.addMergedRegion(disjointRegion); //allowed
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test adding merged regions. If the region's bounds are outside of the allowed range
|
* Test adding merged regions. If the region's bounds are outside of the allowed range
|
||||||
* then an IllegalArgumentException should be thrown
|
* then an IllegalArgumentException should be thrown
|
||||||
@ -310,13 +350,13 @@ public abstract class BaseTestSheet {
|
|||||||
Sheet sheet = wb.createSheet();
|
Sheet sheet = wb.createSheet();
|
||||||
CellRangeAddress region = new CellRangeAddress(0, 1, 0, 1);
|
CellRangeAddress region = new CellRangeAddress(0, 1, 0, 1);
|
||||||
sheet.addMergedRegion(region);
|
sheet.addMergedRegion(region);
|
||||||
region = new CellRangeAddress(1, 2, 0, 1);
|
region = new CellRangeAddress(2, 3, 0, 1);
|
||||||
sheet.addMergedRegion(region);
|
sheet.addMergedRegion(region);
|
||||||
|
|
||||||
sheet.removeMergedRegion(0);
|
sheet.removeMergedRegion(0);
|
||||||
|
|
||||||
region = sheet.getMergedRegion(0);
|
region = sheet.getMergedRegion(0);
|
||||||
assertEquals("Left over region should be starting at row 1", 1, region.getFirstRow());
|
assertEquals("Left over region should be starting at row 2", 2, region.getFirstRow());
|
||||||
|
|
||||||
sheet.removeMergedRegion(0);
|
sheet.removeMergedRegion(0);
|
||||||
|
|
||||||
|
@ -448,7 +448,7 @@ public abstract class BaseTestWorkbook {
|
|||||||
sheet.createRow(0).createCell(0).setCellValue("Test");
|
sheet.createRow(0).createCell(0).setCellValue("Test");
|
||||||
sheet.createRow(1).createCell(0).setCellValue(36.6);
|
sheet.createRow(1).createCell(0).setCellValue(36.6);
|
||||||
sheet.addMergedRegion(new CellRangeAddress(0, 1, 0, 2));
|
sheet.addMergedRegion(new CellRangeAddress(0, 1, 0, 2));
|
||||||
sheet.addMergedRegion(new CellRangeAddress(1, 2, 0, 2));
|
sheet.addMergedRegion(new CellRangeAddress(2, 3, 0, 2));
|
||||||
assertTrue(sheet.isSelected());
|
assertTrue(sheet.isSelected());
|
||||||
|
|
||||||
Sheet clonedSheet = book.cloneSheet(0);
|
Sheet clonedSheet = book.cloneSheet(0);
|
||||||
@ -457,16 +457,16 @@ public abstract class BaseTestWorkbook {
|
|||||||
assertEquals(2, clonedSheet.getNumMergedRegions());
|
assertEquals(2, clonedSheet.getNumMergedRegions());
|
||||||
assertFalse(clonedSheet.isSelected());
|
assertFalse(clonedSheet.isSelected());
|
||||||
|
|
||||||
//cloned sheet is a deep copy, adding rows in the original does not affect the clone
|
//cloned sheet is a deep copy, adding rows or merged regions in the original does not affect the clone
|
||||||
sheet.createRow(2).createCell(0).setCellValue(1);
|
sheet.createRow(2).createCell(0).setCellValue(1);
|
||||||
sheet.addMergedRegion(new CellRangeAddress(0, 2, 0, 2));
|
sheet.addMergedRegion(new CellRangeAddress(4, 5, 0, 2));
|
||||||
assertEquals(2, clonedSheet.getPhysicalNumberOfRows());
|
|
||||||
assertEquals(2, clonedSheet.getPhysicalNumberOfRows());
|
assertEquals(2, clonedSheet.getPhysicalNumberOfRows());
|
||||||
|
assertEquals(2, clonedSheet.getNumMergedRegions());
|
||||||
|
|
||||||
clonedSheet.createRow(2).createCell(0).setCellValue(1);
|
clonedSheet.createRow(2).createCell(0).setCellValue(1);
|
||||||
clonedSheet.addMergedRegion(new CellRangeAddress(0, 2, 0, 2));
|
clonedSheet.addMergedRegion(new CellRangeAddress(6, 7, 0, 2));
|
||||||
assertEquals(3, clonedSheet.getPhysicalNumberOfRows());
|
|
||||||
assertEquals(3, clonedSheet.getPhysicalNumberOfRows());
|
assertEquals(3, clonedSheet.getPhysicalNumberOfRows());
|
||||||
|
assertEquals(3, clonedSheet.getNumMergedRegions());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||||||
|
|
||||||
package org.apache.poi.ss.util;
|
package org.apache.poi.ss.util;
|
||||||
|
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -233,4 +235,32 @@ public final class TestCellRangeAddress extends TestCase {
|
|||||||
assertEquals(4, ref.getMinColumn());
|
assertEquals(4, ref.getMinColumn());
|
||||||
assertEquals(10, ref.getMaxColumn());
|
assertEquals(10, ref.getMaxColumn());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testIntersects() {
|
||||||
|
final CellRangeAddress baseRegion = new CellRangeAddress(0, 1, 0, 1);
|
||||||
|
|
||||||
|
final CellRangeAddress duplicateRegion = new CellRangeAddress(0, 1, 0, 1);
|
||||||
|
assertIntersects(baseRegion, duplicateRegion);
|
||||||
|
|
||||||
|
final CellRangeAddress partiallyOverlappingRegion = new CellRangeAddress(1, 2, 1, 2);
|
||||||
|
assertIntersects(baseRegion, partiallyOverlappingRegion);
|
||||||
|
|
||||||
|
final CellRangeAddress subsetRegion = new CellRangeAddress(0, 1, 0, 0);
|
||||||
|
assertIntersects(baseRegion, subsetRegion);
|
||||||
|
|
||||||
|
final CellRangeAddress supersetRegion = new CellRangeAddress(0, 2, 0, 2);
|
||||||
|
assertIntersects(baseRegion, supersetRegion);
|
||||||
|
|
||||||
|
final CellRangeAddress disjointRegion = new CellRangeAddress(10, 11, 10, 11);
|
||||||
|
assertNotIntersects(baseRegion, disjointRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertIntersects(CellRangeAddress regionA, CellRangeAddress regionB) {
|
||||||
|
assertTrue(regionA.intersects(regionB));
|
||||||
|
assertTrue(regionB.intersects(regionA));
|
||||||
|
}
|
||||||
|
private static void assertNotIntersects(CellRangeAddress regionA, CellRangeAddress regionB) {
|
||||||
|
assertFalse(regionA.intersects(regionB));
|
||||||
|
assertFalse(regionB.intersects(regionA));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user