View | Details | Raw Unified | Return to bug 56454
Collapse All | Expand All

(-)src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java (-2 / +2 lines)
Lines 1385-1392 Link Here
1385
            }
1385
            }
1386
1386
1387
            //only shift if the region outside the shifted rows is not merged too
1387
            //only shift if the region outside the shifted rows is not merged too
1388
            if (!SheetUtil.containsCell(merged, startRow - 1, 0) &&
1388
            if (!merged.containsRow(startRow - 1) &&
1389
                    !SheetUtil.containsCell(merged, endRow + 1, 0)) {
1389
                    !merged.containsRow(endRow + 1)) {
1390
                merged.setFirstRow(merged.getFirstRow() + n);
1390
                merged.setFirstRow(merged.getFirstRow() + n);
1391
                merged.setLastRow(merged.getLastRow() + n);
1391
                merged.setLastRow(merged.getLastRow() + n);
1392
                //have to remove/add it back
1392
                //have to remove/add it back
(-)src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java (-2 / +55 lines)
Lines 396-401 Link Here
396
            }
396
            }
397
        }
397
        }
398
    }
398
    }
399
    
400
    /**
401
     * Verify that candidate region does not intersect with an existing, non-ignored merged region in this sheet
402
     *
403
     * @param candidateRegion
404
     * @param indexToIgnore the index of the merged region to ignore
405
     * @throws IllegalStateException if candidate region intersects an existing, non-ignored merged region in this sheet
406
     */
407
    private void validateMergedRegions(CellRangeAddress candidateRegion, int indexToIgnore) {
408
        List<CellRangeAddress> mergedRegions = getMergedRegions();
409
        mergedRegions.remove(indexToIgnore);
410
        for (final CellRangeAddress existingRegion : getMergedRegions()) {
411
            if (existingRegion.intersects(candidateRegion)) {
412
                throw new IllegalStateException("Cannot add merged region " + candidateRegion.formatAsString() +
413
                        " to sheet because it overlaps with an existing merged region (" + existingRegion.formatAsString() + ").");
414
            }
415
        }
416
    }
399
417
400
    /**
418
    /**
401
     * Adjusts the column width to fit the contents.
419
     * Adjusts the column width to fit the contents.
Lines 1154-1159 Link Here
1154
     * Returns the merged region at the specified index. If you want multiple
1172
     * Returns the merged region at the specified index. If you want multiple
1155
     * regions, it is faster to call {@link #getMergedRegions()} than to call
1173
     * regions, it is faster to call {@link #getMergedRegions()} than to call
1156
     * this each time.
1174
     * this each time.
1175
     * Modifying the returned merged region will not affect the merged regions in the sheet.
1176
     * FIXME: Perhaps this should return an UnmodifiableCellRangeAddress to eliminate confusion?
1157
     *
1177
     *
1158
     * @return the merged region at the specified index
1178
     * @return the merged region at the specified index
1159
     */
1179
     */
Lines 1751-1757 Link Here
1751
            worksheet.unsetMergeCells();
1771
            worksheet.unsetMergeCells();
1752
        }
1772
        }
1753
    }
1773
    }
1754
1774
    
1755
    /**
1775
    /**
1756
     * Removes a number of merged regions of cells (hence letting them free)
1776
     * Removes a number of merged regions of cells (hence letting them free)
1757
     *
1777
     *
Lines 1769-1775 Link Here
1769
        List<CTMergeCell> newMergeCells = new ArrayList<CTMergeCell>(ctMergeCells.sizeOfMergeCellArray());
1789
        List<CTMergeCell> newMergeCells = new ArrayList<CTMergeCell>(ctMergeCells.sizeOfMergeCellArray());
1770
1790
1771
        int idx = 0;
1791
        int idx = 0;
1772
        for (CTMergeCell mc : ctMergeCells.getMergeCellArray()) {
1792
        for (CTMergeCell mc : ctMergeCells.getMergeCellList()) {
1773
            if (!indices.contains(idx++)) newMergeCells.add(mc);
1793
            if (!indices.contains(idx++)) newMergeCells.add(mc);
1774
        }
1794
        }
1775
        
1795
        
Lines 1780-1785 Link Here
1780
            ctMergeCells.setMergeCellArray(newMergeCells.toArray(newMergeCellsArray));
1800
            ctMergeCells.setMergeCellArray(newMergeCells.toArray(newMergeCellsArray));
1781
        }
1801
        }
1782
    }
1802
    }
1803
    
1804
    /**
1805
     * Replace a merged region with a different merged region.
1806
     * This is faster than calling <code>removeMergedRegion(int)</code>
1807
     * and <code>addMergedRegion(CellRangeAddress)</code>
1808
     *
1809
     * @param index of the region to unmerge
1810
     * @param 
1811
     */
1812
    //@Override
1813
    public void replaceMergedRegion(int index, CellRangeAddress region) {
1814
        region.validate(SpreadsheetVersion.EXCEL2007);
1815
        
1816
        // throw IllegalStateException if the argument CellRangeAddress intersects with
1817
        // a multi-cell array formula defined in this sheet
1818
        validateArrayFormulas(region);
1819
        
1820
        if (!worksheet.isSetMergeCells()) {
1821
            throw new IllegalStateException("Worksheet has no merged cells to replace.");
1822
        }
1823
        
1824
        CTMergeCells ctMergeCells = worksheet.getMergeCells();
1825
        int size = ctMergeCells.sizeOfMergeCellArray();
1826
        assert(0 <= index && index < size);
1827
        CTMergeCell ctMergeCell = ctMergeCells.getMergeCellArray(index);
1828
        
1829
        // Throw IllegalStateException if the argument CellRangeAddress intersects with
1830
        // a merged region already in this sheet 
1831
        validateMergedRegions(region, index);
1832
        
1833
        String ref = region.formatAsString();
1834
        ctMergeCell.setRef(ref);
1835
    }
1783
1836
1784
    /**
1837
    /**
1785
     * Remove a row from this sheet.  All cells contained in the row are removed as well
1838
     * Remove a row from this sheet.  All cells contained in the row are removed as well
(-)src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java (-30 / +111 lines)
Lines 18-26 Link Here
18
package org.apache.poi.xssf.usermodel.helpers;
18
package org.apache.poi.xssf.usermodel.helpers;
19
19
20
import java.util.ArrayList;
20
import java.util.ArrayList;
21
import java.util.HashSet;
21
import java.util.Collection;
22
import java.util.Collections;
22
import java.util.List;
23
import java.util.List;
23
import java.util.Set;
24
import java.util.Map;
25
import java.util.Map.Entry;
26
import java.util.TreeMap;
24
27
25
import org.apache.poi.ss.formula.FormulaParseException;
28
import org.apache.poi.ss.formula.FormulaParseException;
26
import org.apache.poi.ss.formula.FormulaParser;
29
import org.apache.poi.ss.formula.FormulaParser;
Lines 71-83 Link Here
71
     * @return an array of affected cell regions
74
     * @return an array of affected cell regions
72
     */
75
     */
73
    public List<CellRangeAddress> shiftMerged(int startRow, int endRow, int n) {
76
    public List<CellRangeAddress> shiftMerged(int startRow, int endRow, int n) {
74
        List<CellRangeAddress> shiftedRegions = new ArrayList<CellRangeAddress>();
77
        Map<Integer, CellRangeAddress> modifiedRegions = new TreeMap<Integer, CellRangeAddress>();
75
        Set<Integer> removedIndices = new HashSet<Integer>();
78
        List<CellRangeAddress> removedRegions = new ArrayList<CellRangeAddress>();
79
        Collection<Integer> modifiedIndices = new ArrayList<Integer>(); //overwritten
80
        
81
        if (n == 0) {
82
            return Collections.emptyList();
83
        }
84
        
85
        int destStartRow = startRow + n;
86
        int destEndRow = endRow + n;
87
        
76
        //move merged regions completely if they fall within the new region boundaries when they are shifted
88
        //move merged regions completely if they fall within the new region boundaries when they are shifted
77
        int size = sheet.getNumMergedRegions();
89
        final int numMergedRegions = sheet.getNumMergedRegions();
78
        for (int i = 0; i < size; i++) {
90
        for (int i = 0; i < numMergedRegions; i++) {
79
            CellRangeAddress merged = sheet.getMergedRegion(i);
91
            CellRangeAddress merged = sheet.getMergedRegion(i);
92
            
93
            // Behavior: Unaffected
94
            // Condition: if shift source and destination rows do not overlap with merged region
95
            boolean mergedRegionNotInSourceRows = (endRow < merged.getFirstRow() || merged.getLastRow() < startRow);
96
            boolean mergedRegionNotInDestRows = (destEndRow < merged.getFirstRow() || merged.getLastRow() < destStartRow);
97
            if (mergedRegionNotInSourceRows && mergedRegionNotInDestRows) {
98
                continue;
99
            }
100
            // Condition: or if source and destination rows fully inside merged region
101
            boolean sourceRowsInMergedRegion = (merged.getFirstRow() < startRow && endRow < merged.getLastRow());
102
            boolean destRowsInMergedRegion = (merged.getFirstRow() < destStartRow && destEndRow < merged.getLastRow());
103
            if (sourceRowsInMergedRegion && destRowsInMergedRegion) {
104
                continue;
105
            }
106
            
107
            // CellRangeAddresses cannot be modified in place.
108
            // At this point, any CellRangeAddress will be modified or removed.
109
            modifiedIndices.add(i);
110
            
111
            // Behavior: Merged region gets deleted
112
            // Condition: if merged region is outside shift source rows but fully or partially contained within shift destination rows
113
            // Condition: if destination rows contain merged regions, merged regions would be shrunk or broken up. Remove them if there's any conflict.
114
            boolean mergedRegionInDestRows = (destStartRow <= merged.getLastRow() && merged.getFirstRow() <= destEndRow);
115
            if (mergedRegionInDestRows) {
116
                removedRegions.add(i, merged);
117
                continue;
118
            }
119
            
120
            // At this point, any CellRangeAddress will be modified.
121
            modifiedRegions.put(i, merged);
122
            
123
            // Behavior: Entire merged region gets shifted down
124
            // Condition: if shift source rows fully contain the merged region
125
            if (startRow <= merged.getFirstRow() && merged.getLastRow() <= endRow) {
126
                merged.setFirstRow(merged.getFirstRow() + n);
127
                merged.setLastRow(merged.getLastRow() + n);
128
                continue;
129
            }
130
            
131
            // Behavior: Merged region gets elongated
132
            // Condition: if startRow is inside the merged region and not the first row of the region (which would result in a shift) and n>0
133
            if (n > 0) {
134
                boolean startRowInMergedRegion = merged.getFirstRow() < startRow && startRow <= merged.getLastRow();
135
                boolean endRowAfterMergedRegion = merged.getLastRow() <= endRow;
136
                if (startRowInMergedRegion && endRowAfterMergedRegion) {
137
                    merged.setLastRow(merged.getLastRow() + n);
138
                    continue;
139
                }
140
            }
141
            else {
142
             // Condition: if endRow is inside the merged region and not the last row of the region (which would result in a shift) and n>0
143
                boolean endRowInMergedRegion = merged.getFirstRow() <= endRow && endRow < merged.getLastRow();
144
                boolean startRowBeforeMergedRegion = startRow <= merged.getFirstRow();
145
                if (endRowInMergedRegion && startRowBeforeMergedRegion) {
146
                    merged.setFirstRow(merged.getFirstRow() + n);
147
                    continue;
148
                }
149
            }
150
            
151
            // Behavior: Merged region gets shrunk
152
            // Condition: startRow is moved down or endRow is moved up
153
            boolean mergedRegionShrunkFromTop = (startRow == merged.getFirstRow() && merged.getLastRow() <= destEndRow);
154
            if (mergedRegionShrunkFromTop) {
155
                merged.setFirstRow(destStartRow);
156
                continue;
157
            }
158
            boolean mergedRegionShrunkFromBottom = (destStartRow <= merged.getLastRow() && merged.getLastRow() == endRow);
159
            if (mergedRegionShrunkFromBottom) {
160
                merged.setLastRow(destEndRow);
161
                continue;
162
            }
163
            
80
164
165
166
            /*
81
            boolean inStart = (merged.getFirstRow() >= startRow || merged.getLastRow() >= startRow);
167
            boolean inStart = (merged.getFirstRow() >= startRow || merged.getLastRow() >= startRow);
82
            boolean inEnd = (merged.getFirstRow() <= endRow || merged.getLastRow() <= endRow);
168
            boolean inEnd = (merged.getFirstRow() <= endRow || merged.getLastRow() <= endRow);
83
169
Lines 87-129 Link Here
87
            }
173
            }
88
174
89
            //only shift if the region outside the shifted rows is not merged too
175
            //only shift if the region outside the shifted rows is not merged too
90
            if (!containsCell(merged, startRow - 1, 0) && !containsCell(merged, endRow + 1, 0)) {
176
            if (!merged.containsRow(startRow - 1) && !merged.containsRow(endRow + 1)) {
91
                merged.setFirstRow(merged.getFirstRow() + n);
177
                merged.setFirstRow(merged.getFirstRow() + n);
92
                merged.setLastRow(merged.getLastRow() + n);
178
                merged.setLastRow(merged.getLastRow() + n);
93
                //have to remove/add it back
179
                //have to remove/add it back
94
                shiftedRegions.add(merged);
180
                shiftedRegions.add(merged);
95
                removedIndices.add(i);
181
                removedIndices.add(i);
96
            }
182
            }*/
97
        }
183
        }
98
        
184
        
99
        if(!removedIndices.isEmpty()) {
185
        // FIXME: would be more efficient to remove only the merged regions that were deleted
100
            sheet.removeMergedRegions(removedIndices);
186
        // and modify existing merged regions in-place (probably something like Sheet.replaceMergedRegion(int index, CellRangeAddress replacement))
187
        
188
        for (Entry<Integer, CellRangeAddress> entry : modifiedRegions.entrySet()) {
189
            sheet.replaceMergedRegion(entry.getKey(), entry.getValue());
101
        }
190
        }
191
        
192
        // Remove all merged regions that were modified or deleted
193
        if(!modifiedIndices.isEmpty()) {
194
            sheet.removeMergedRegions(modifiedIndices);
195
        }
102
196
103
        //read so it doesn't get shifted again
197
        /*// Add back merged regions that were modified
104
        for (CellRangeAddress region : shiftedRegions) {
198
        for (CellRangeAddress region : modifiedRegions) {
105
            sheet.addMergedRegion(region);
199
            sheet.addMergedRegion(region);
106
        }
200
        }*/
107
        return shiftedRegions;
201
        // Include deleted regions in the return value
202
        List<CellRangeAddress> affectedRegions = new ArrayList<CellRangeAddress>(modifiedRegions.values());
203
        affectedRegions.addAll(removedRegions);
204
        return affectedRegions;
108
    }
205
    }
109
206
110
    /**
207
    /**
111
     * Check if the  row and column are in the specified cell range
112
     *
113
     * @param cr    the cell range to check in
114
     * @param rowIx the row to check
115
     * @param colIx the column to check
116
     * @return true if the range contains the cell [rowIx,colIx]
117
     */
118
    private static boolean containsCell(CellRangeAddress cr, int rowIx, int colIx) {
119
        if (cr.getFirstRow() <= rowIx && cr.getLastRow() >= rowIx
120
                && cr.getFirstColumn() <= colIx && cr.getLastColumn() >= colIx) {
121
            return true;
122
        }
123
        return false;
124
    }
125
126
    /**
127
     * Updated named ranges
208
     * Updated named ranges
128
     */
209
     */
129
    public void updateNamedRanges(FormulaShifter shifter) {
210
    public void updateNamedRanges(FormulaShifter shifter) {
(-)src/java/org/apache/poi/ss/util/CellRangeAddressBase.java (-2 / +21 lines)
Lines 122-132 Link Here
122
	 * @return True if the coordinates lie within the bounds, false otherwise.
122
	 * @return True if the coordinates lie within the bounds, false otherwise.
123
	 */
123
	 */
124
	public boolean isInRange(int rowInd, int colInd) {
124
	public boolean isInRange(int rowInd, int colInd) {
125
		return _firstRow <= rowInd && rowInd <= _lastRow &&
125
		return containsRow(rowInd) && containsColumn(colInd);
126
				_firstCol <= colInd && colInd <= _lastCol;
127
	}
126
	}
128
	
127
	
129
	/**
128
	/**
129
	 * Check if the row is in the specified cell range
130
	 *
131
	 * @param rowInd the row to check
132
	 * @return true if the range contains the row [rowInd]
133
	 */
134
	public boolean containsRow(int rowInd) {
135
		return _firstRow <= rowInd && rowInd <= _lastRow;
136
	}
137
	
138
	/**
139
	 * Check if the column is in the specified cell range
140
	 *
141
	 * @param colInd the column to check
142
	 * @return true if the range contains the column [colInd]
143
	 */
144
	public boolean containsColumn(int colInd) {
145
		return _firstCol <= colInd && colInd <= _lastCol;
146
	}
147
	
148
	/**
130
	 * Determines whether or not this CellRangeAddress and the specified CellRangeAddress intersect.
149
	 * Determines whether or not this CellRangeAddress and the specified CellRangeAddress intersect.
131
	 *
150
	 *
132
	 * @param other a candidate cell range address to check for intersection with this range
151
	 * @param other a candidate cell range address to check for intersection with this range
(-)src/java/org/apache/poi/ss/util/SheetUtil.java (-7 / +10 lines)
Lines 318-331 Link Here
318
        if (font.getUnderline() == Font.U_SINGLE ) str.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, startIdx, endIdx);
318
        if (font.getUnderline() == Font.U_SINGLE ) str.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, startIdx, endIdx);
319
    }
319
    }
320
320
321
    /**
322
     * Check if the cell is in the specified cell range
323
     *
324
     * @param cr    the cell range to check in
325
     * @param rowIx the row to check
326
     * @param colIx the column to check
327
     * @return true if the range contains the cell [rowIx, colIx]
328
     * @deprecated 3.14beta2. Use {@link CellRangeAddressBase#isInRange(int, int)} instead.
329
     */
321
    public static boolean containsCell(CellRangeAddress cr, int rowIx, int colIx) {
330
    public static boolean containsCell(CellRangeAddress cr, int rowIx, int colIx) {
322
        //FIXME: isn't this the same as cr.isInRange(rowInd, colInd) ?
331
        return cr.isInRange(rowIx,  colIx);
323
        if (cr.getFirstRow() <= rowIx && cr.getLastRow() >= rowIx
324
                && cr.getFirstColumn() <= colIx && cr.getLastColumn() >= colIx)
325
        {
326
            return true;
327
        }
328
        return false;
329
    }
332
    }
330
333
331
    /**
334
    /**
(-)src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftRows.java (+4 lines)
Lines 325-333 Link Here
325
        
325
        
326
        CellRangeAddress A4_B7 = CellRangeAddress.valueOf("A4:B7");
326
        CellRangeAddress A4_B7 = CellRangeAddress.valueOf("A4:B7");
327
        CellRangeAddress C4_D7 = CellRangeAddress.valueOf("C4:D7");
327
        CellRangeAddress C4_D7 = CellRangeAddress.valueOf("C4:D7");
328
        //CellRangeAddress C5_D7 = new CellRangeAddress(4, 6, 2, 3);
328
        
329
        
329
        sheet.addMergedRegion(A4_B7);
330
        sheet.addMergedRegion(A4_B7);
330
        sheet.addMergedRegion(C4_D7);
331
        sheet.addMergedRegion(C4_D7);
332
        //sheet.addMergedRegion(C5_D7);
331
        
333
        
332
        assumeTrue(sheet.getLastRowNum() > 8);
334
        assumeTrue(sheet.getLastRowNum() > 8);
333
        
335
        
Lines 338-345 Link Here
338
        List<CellRangeAddress> expectedMergedRegions = new ArrayList<CellRangeAddress>();
340
        List<CellRangeAddress> expectedMergedRegions = new ArrayList<CellRangeAddress>();
339
        CellRangeAddress A4_B8 = CellRangeAddress.valueOf("A4:B8"); //A4:B7 should be elongated by 1 row
341
        CellRangeAddress A4_B8 = CellRangeAddress.valueOf("A4:B8"); //A4:B7 should be elongated by 1 row
340
        CellRangeAddress C4_D8 = CellRangeAddress.valueOf("C4:D8"); //C4:B7 should be elongated by 1 row
342
        CellRangeAddress C4_D8 = CellRangeAddress.valueOf("C4:D8"); //C4:B7 should be elongated by 1 row
343
        //CellRangeAddress C6_D8 = new CellRangeAddress(5, 7, 2, 3); //C5:D7 should be shifted by one row, remain same size
341
        expectedMergedRegions.add(A4_B8);
344
        expectedMergedRegions.add(A4_B8);
342
        expectedMergedRegions.add(C4_D8);
345
        expectedMergedRegions.add(C4_D8);
346
        //expectedMergedRegions.add(C6_D8);
343
        
347
        
344
        assertEquals(expectedMergedRegions, sheet.getMergedRegions());
348
        assertEquals(expectedMergedRegions, sheet.getMergedRegions());
345
    }
349
    }

Return to bug 56454