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

(-)src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java (-40 / +102 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-129 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);
80
92
            
81
            boolean inStart = (merged.getFirstRow() >= startRow || merged.getLastRow() >= startRow);
93
            // Behavior: Unaffected
82
            boolean inEnd = (merged.getFirstRow() <= endRow || merged.getLastRow() <= endRow);
94
            // Condition: if shift source and destination rows do not overlap with merged region
83
95
            boolean mergedRegionNotInSourceRows = (endRow < merged.getFirstRow() || merged.getLastRow() < startRow);
84
            //don't check if it's not within the shifted area
96
            boolean mergedRegionNotInDestRows = (destEndRow < merged.getFirstRow() || merged.getLastRow() < destStartRow);
85
            if (!inStart || !inEnd) {
97
            if (mergedRegionNotInSourceRows && mergedRegionNotInDestRows) {
86
                continue;
98
                continue;
87
            }
99
            }
88
100
            // Condition: or if source and destination rows fully inside merged region
89
            //only shift if the region outside the shifted rows is not merged too
101
            boolean sourceRowsInMergedRegion = (merged.getFirstRow() < startRow && endRow < merged.getLastRow());
90
            if (!containsCell(merged, startRow - 1, 0) && !containsCell(merged, endRow + 1, 0)) {
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) {
91
                merged.setFirstRow(merged.getFirstRow() + n);
126
                merged.setFirstRow(merged.getFirstRow() + n);
92
                merged.setLastRow(merged.getLastRow() + n);
127
                merged.setLastRow(merged.getLastRow() + n);
93
                //have to remove/add it back
128
                continue;
94
                shiftedRegions.add(merged);
95
                removedIndices.add(i);
96
            }
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
            }
97
        }
163
        }
98
        
164
        
99
        if(!removedIndices.isEmpty()) {
165
        // probably need to replace all merged regions atomically and only verify if there are 
100
            sheet.removeMergedRegions(removedIndices);
166
        // overlapping merged regions after all replacements and removals are done, then either
167
        // roll back all changes and throw an exception if there are overlapping merged regions
168
        // or delete the merged regions that are invalidated due to the shift
169
        for (Entry<Integer, CellRangeAddress> entry : modifiedRegions.entrySet()) {
170
            sheet.replaceMergedRegion(entry.getKey(), entry.getValue());
101
        }
171
        }
172
        
173
        // Remove all merged regions that were modified or deleted
174
        if(!modifiedIndices.isEmpty()) {
175
            sheet.removeMergedRegions(modifiedIndices);
176
        }
102
177
103
        //read so it doesn't get shifted again
178
        /*// Add back merged regions that were modified
104
        for (CellRangeAddress region : shiftedRegions) {
179
        for (CellRangeAddress region : modifiedRegions) {
105
            sheet.addMergedRegion(region);
180
            sheet.addMergedRegion(region);
106
        }
181
        }*/
107
        return shiftedRegions;
182
        // Include deleted regions in the return value
183
        List<CellRangeAddress> affectedRegions = new ArrayList<CellRangeAddress>(modifiedRegions.values());
184
        affectedRegions.addAll(removedRegions);
185
        return affectedRegions;
108
    }
186
    }
109
187
110
    /**
188
    /**
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
189
     * Updated named ranges
128
     */
190
     */
129
    public void updateNamedRanges(FormulaShifter shifter) {
191
    public void updateNamedRanges(FormulaShifter shifter) {
(-)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/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/java/org/apache/poi/ss/util/SheetUtil.java (+1 lines)
Lines 325-330 Link Here
325
     * @param rowIx the row to check
325
     * @param rowIx the row to check
326
     * @param colIx the column to check
326
     * @param colIx the column to check
327
     * @return true if the range contains the cell [rowIx, colIx]
327
     * @return true if the range contains the cell [rowIx, colIx]
328
     * @deprecated 3.14beta2. Use {@link CellRangeAddressBase#isInRange(int, int)} instead.
328
     */
329
     */
329
    public static boolean containsCell(CellRangeAddress cr, int rowIx, int colIx) {
330
    public static boolean containsCell(CellRangeAddress cr, int rowIx, int colIx) {
330
        return cr.isInRange(rowIx,  colIx);
331
        return cr.isInRange(rowIx,  colIx);

Return to bug 56454