more improvements in shiftRows: 1. shift named ranges and merged regions, JUnit added. 2. fixed shiftRows in XSSF to preserve row heights, see bugzilla 46719.

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@747894 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2009-02-25 19:12:06 +00:00
parent 55dfa11284
commit bebc51def9
8 changed files with 282 additions and 37 deletions

View File

@ -38,7 +38,7 @@
<p>Development for this is in a svn branch, but we are please to
announce our first preview release containing this support.
Users interested in the OOXML support should download the
<link href="http://www.apache.org/dyn/closer.cgi/poi/dev/">POI 3.5 beta 4</link>
<link href="http://www.apache.org/dyn/closer.cgi/poi/dev/">POI 3.5 beta 5</link>
the source and binaries from their
<link href="http://www.apache.org/dyn/closer.cgi/poi/dev/">local mirror</link>.
People interested should also follow the

View File

@ -79,6 +79,8 @@ import org.apache.poi.hssf.record.WindowProtectRecord;
import org.apache.poi.hssf.record.WriteAccessRecord;
import org.apache.poi.hssf.record.WriteProtectRecord;
import org.apache.poi.hssf.record.formula.NameXPtg;
import org.apache.poi.hssf.record.formula.FormulaShifter;
import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
import org.apache.poi.util.POILogFactory;
@ -2310,4 +2312,18 @@ public final class Workbook implements Model {
}
}
/**
* Updates named ranges due to moving of cells
*/
public void updateNamesAfterCellShift(FormulaShifter shifter) {
for (int i = 0 ; i < getNumNames() ; ++i){
NameRecord nr = getNameRecord(i);
Ptg[] ptgs = nr.getNameDefinition();
if (shifter.adjustFormula(ptgs, nr.getExternSheetNumber())) {
nr.setNameDefinition(ptgs);
}
}
}
}

View File

@ -1285,7 +1285,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet {
short otherExtSheetIx = _book.checkExternSheet(i);
otherSheet.updateFormulasAfterCellShift(shifter, otherExtSheetIx);
}
// TODO - adjust formulas in named ranges
_workbook.getWorkbook().updateNamesAfterCellShift(shifter);
}
protected void insertChartRecords(List<Record> records) {

View File

@ -37,6 +37,7 @@ import org.apache.poi.ss.formula.FormulaRenderer;
import org.apache.poi.xssf.model.CommentsTable;
import org.apache.poi.xssf.model.CalculationChain;
import org.apache.poi.xssf.usermodel.helpers.ColumnHelper;
import org.apache.poi.xssf.usermodel.helpers.XSSFRowShifter;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.POIXMLException;
import org.apache.poi.util.POILogger;
@ -74,7 +75,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
protected CTSheet sheet;
protected CTWorksheet worksheet;
private TreeMap<Integer, Row> rows;
private TreeMap<Integer, XSSFRow> rows;
private List<XSSFHyperlink> hyperlinks;
private ColumnHelper columnHelper;
private CommentsTable sheetComments;
@ -151,7 +152,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
}
private void initRows(CTWorksheet worksheet) {
rows = new TreeMap<Integer, Row>();
rows = new TreeMap<Integer, XSSFRow>();
sharedFormulas = new HashMap<Integer, XSSFCell>();
for (CTRow row : worksheet.getSheetData().getRowArray()) {
XSSFRow r = new XSSFRow(row, this);
@ -831,7 +832,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
* @return <code>XSSFRow</code> representing the rownumber or <code>null</code> if its not defined on the sheet
*/
public XSSFRow getRow(int rownum) {
return (XSSFRow)rows.get(rownum);
return rows.get(rownum);
}
/**
@ -1012,8 +1013,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
private short getMaxOutlineLevelRows(){
short outlineLevel=0;
for(Row r : rows.values()){
XSSFRow xrow=(XSSFRow)r;
for(XSSFRow xrow : rows.values()){
outlineLevel=xrow.getCTRow().getOutlineLevel()>outlineLevel? xrow.getCTRow().getOutlineLevel(): outlineLevel;
}
return outlineLevel;
@ -1224,7 +1224,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
* Call getRowNum() on each row if you care which one it is.
*/
public Iterator<Row> rowIterator() {
return rows.values().iterator();
return (Iterator<Row>)(Iterator<? extends Row>)rows.values().iterator();
}
/**
@ -1466,18 +1466,16 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
for (Iterator<Row> it = rowIterator() ; it.hasNext() ; ) {
XSSFRow row = (XSSFRow)it.next();
int rownum = row.getRowNum();
if(rownum < startRow) continue;
if (!copyRowHeight) {
row.setHeight((short)-1);
}
if (resetOriginalRowHeight && getDefaultRowHeight() >= 0) {
row.setHeight(getDefaultRowHeight());
}
if (removeRow(startRow, endRow, n, row.getRowNum())) {
if (removeRow(startRow, endRow, n, rownum)) {
it.remove();
}
else if (row.getRowNum() >= startRow && row.getRowNum() <= endRow) {
else if (rownum >= startRow && rownum <= endRow) {
row.shift(n);
}
@ -1493,26 +1491,21 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
}
}
}
//rebuild the rows map
XSSFRowShifter rowShifter = new XSSFRowShifter(this);
int sheetIndex = getWorkbook().getSheetIndex(this);
FormulaShifter shifter = FormulaShifter.createForRowShift(sheetIndex, startRow, endRow, n);
TreeMap<Integer, Row> map = new TreeMap<Integer, Row>();
for(Row r : this) {
XSSFRow row = (XSSFRow)r;
row.updateFormulasAfterCellShift(shifter);
rowShifter.updateNamedRanges(shifter);
rowShifter.updateFormulas(shifter);
rowShifter.shiftMerged(startRow, endRow, n);
//rebuild the rows map
TreeMap<Integer, XSSFRow> map = new TreeMap<Integer, XSSFRow>();
for(XSSFRow r : rows.values()) {
map.put(r.getRowNum(), r);
}
rows = map;
//update formulas on other sheets
for(XSSFSheet sheet : getWorkbook()) {
if (sheet == this) continue;
for(Row r : sheet) {
XSSFRow row = (XSSFRow)r;
row.updateFormulasAfterCellShift(shifter);
}
}
}
/**
@ -1783,10 +1776,9 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
CTSheetData sheetData = worksheet.getSheetData();
ArrayList<CTRow> rArray = new ArrayList<CTRow>(rows.size());
for(Row row : rows.values()){
XSSFRow r = (XSSFRow)row;
r.onDocumentWrite();
rArray.add(r.getCTRow());
for(XSSFRow row : rows.values()){
row.onDocumentWrite();
rArray.add(row.getCTRow());
}
sheetData.setRowArray(rArray.toArray(new CTRow[rArray.size()]));

View File

@ -0,0 +1,189 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.xssf.usermodel.helpers;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.formula.FormulaParser;
import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.ss.formula.FormulaRenderer;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.hssf.record.formula.FormulaShifter;
import org.apache.poi.hssf.record.formula.Ptg;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCellFormula;
import java.util.List;
import java.util.ArrayList;
/**
* @author Yegor Kozlov
*/
public class XSSFRowShifter {
private final XSSFSheet sheet;
public XSSFRowShifter(XSSFSheet sh) {
sheet = sh;
}
/**
* Shift merged regions
*
* @param startRow the row to start shifting
* @param endRow the row to end shifting
* @param n the number of rows to shift
* @return an array of affected cell regions
*/
public List<CellRangeAddress> shiftMerged(int startRow, int endRow, int n) {
List<CellRangeAddress> shiftedRegions = new ArrayList<CellRangeAddress>();
//move merged regions completely if they fall within the new region boundaries when they are shifted
for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
CellRangeAddress merged = sheet.getMergedRegion(i);
boolean inStart = (merged.getFirstRow() >= startRow || merged.getLastRow() >= startRow);
boolean inEnd = (merged.getFirstRow() <= endRow || merged.getLastRow() <= endRow);
//don't check if it's not within the shifted area
if (!inStart || !inEnd) {
continue;
}
//only shift if the region outside the shifted rows is not merged too
if (!containsCell(merged, startRow - 1, 0) && !containsCell(merged, endRow + 1, 0)) {
merged.setFirstRow(merged.getFirstRow() + n);
merged.setLastRow(merged.getLastRow() + n);
//have to remove/add it back
shiftedRegions.add(merged);
sheet.removeMergedRegion(i);
i = i - 1; // we have to back up now since we removed one
}
}
//read so it doesn't get shifted again
for (CellRangeAddress region : shiftedRegions) {
sheet.addMergedRegion(region);
}
return shiftedRegions;
}
/**
* Check if the row and column are in the specified cell range
*
* @param cr the cell range to check in
* @param rowIx the row to check
* @param colIx the column to check
* @return true if the range contains the cell [rowIx,colIx]
*/
private static boolean containsCell(CellRangeAddress cr, int rowIx, int colIx) {
if (cr.getFirstRow() <= rowIx && cr.getLastRow() >= rowIx
&& cr.getFirstColumn() <= colIx && cr.getLastColumn() >= colIx) {
return true;
}
return false;
}
/**
* Updated named ranges
*/
public void updateNamedRanges(FormulaShifter shifter) {
XSSFWorkbook wb = sheet.getWorkbook();
int sheetIndex = wb.getSheetIndex(sheet);
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
for (int i = 0; i < wb.getNumberOfNames(); i++) {
XSSFName name = wb.getNameAt(i);
String formula = name.getRefersToFormula();
Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex);
if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
name.setRefersToFormula(shiftedFmla);
}
}
}
/**
* Update formulas.
*/
public void updateFormulas(FormulaShifter shifter) {
//update formulas on the parent sheet
updateSheetFormulas(sheet, shifter);
//update formulas on other sheets
XSSFWorkbook wb = sheet.getWorkbook();
for (XSSFSheet sh : wb) {
if (sheet == sh) continue;
updateSheetFormulas(sh, shifter);
}
}
private void updateSheetFormulas(XSSFSheet sh, FormulaShifter shifter) {
for (Row r : sh) {
XSSFRow row = (XSSFRow) r;
updateRowFormulas(row, shifter);
}
}
private void updateRowFormulas(XSSFRow row, FormulaShifter shifter) {
for (Cell c : row) {
XSSFCell cell = (XSSFCell) c;
CTCell ctCell = cell.getCTCell();
if (ctCell.isSetF()) {
CTCellFormula f = ctCell.getF();
String formula = f.getStringValue();
if (formula.length() > 0) {
String shiftedFormula = shiftFormula(row, formula, shifter);
if (shiftedFormula != null) {
f.setStringValue(shiftedFormula);
}
}
if (f.isSetRef()) { //Range of cells which the formula applies to.
String ref = f.getRef();
String shiftedRef = shiftFormula(row, ref, shifter);
if (shiftedRef != null) f.setRef(shiftedRef);
}
}
}
}
/**
* Shift a formula using the supplied FormulaShifter
*
* @param row the row of the cell this formula belongs to. Used to get a reference to the parent workbook.
* @param formula the formula to shift
* @param shifter the FormulaShifter object that operates on the parsed formula tokens
* @return the shifted formula if the formula was changed,
* <code>null</code> if the formula wasn't modified
*/
private static String shiftFormula(XSSFRow row, String formula, FormulaShifter shifter) {
XSSFSheet sheet = row.getSheet();
XSSFWorkbook wb = sheet.getWorkbook();
int sheetIndex = wb.getSheetIndex(sheet);
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex);
String shiftedFmla = null;
if (shifter.adjustFormula(ptgs, sheetIndex)) {
shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
}
return shiftedFmla;
}
}

View File

@ -38,8 +38,8 @@ public class TestSheetShiftRows extends BaseTestSheetShiftRows {
baseTestShiftRow();
}
public void testShiftRow0() {
baseTestShiftRow0();
public void testShiftNames() {
baseTestShiftWithNames();
}
//TODO support shifting of page breaks
@ -55,4 +55,8 @@ public class TestSheetShiftRows extends BaseTestSheetShiftRows {
public void testShiftWithFormulas() {
baseTestShiftWithFormulas("ForShifting.xlsx");
}
public void testShiftWithMergedRegions() {
baseTestShiftWithMergedRegions();
}
}

View File

@ -43,8 +43,8 @@ public final class TestSheetShiftRows extends BaseTestSheetShiftRows {
baseTestShiftRow();
}
public void testShiftRow0() {
baseTestShiftRow0();
public void testShiftNames() {
baseTestShiftWithNames();
}
public void testShiftRowBreaks() {
@ -59,4 +59,7 @@ public final class TestSheetShiftRows extends BaseTestSheetShiftRows {
baseTestShiftWithFormulas("ForShifting.xls");
}
public void testShiftWithMergedRegions() {
baseTestShiftWithMergedRegions();
}
}

View File

@ -19,6 +19,7 @@ package org.apache.poi.ss.usermodel;
import junit.framework.TestCase;
import org.apache.poi.ss.ITestDataProvider;
import org.apache.poi.ss.util.CellRangeAddress;
/**
* Tests row shifting capabilities.
@ -108,9 +109,10 @@ public abstract class BaseTestSheetShiftRows extends TestCase {
/**
* Tests when shifting the first row.
*/
public final void baseTestShiftRow0() {
public final void baseTestActiveCell() {
Workbook b = getTestDataProvider().createWorkbook();
Sheet s = b.createSheet();
s.createRow(0).createCell(0).setCellValue("TEST1");
s.createRow(3).createCell(0).setCellValue("TEST2");
s.shiftRows(0,4,1);
@ -190,6 +192,45 @@ public abstract class BaseTestSheetShiftRows extends TestCase {
assertEquals(comment4,comment4_shifted);
}
public final void baseTestShiftWithNames() {
Workbook wb = getTestDataProvider().createWorkbook();
Sheet sheet = wb.createSheet();
Row row = sheet.createRow(0);
row.createCell(0).setCellValue(1.1);
row.createCell(1).setCellValue(2.2);
Name name1 = wb.createName();
name1.setNameName("name1");
name1.setRefersToFormula("A1+B1");
Name name2 = wb.createName();
name2.setNameName("name2");
name2.setRefersToFormula("A1");
sheet.shiftRows(0, 1, 2);
name1 = wb.getNameAt(0);
assertEquals("A3+B3", name1.getRefersToFormula());
name2 = wb.getNameAt(1);
assertEquals("A3", name2.getRefersToFormula());
}
public final void baseTestShiftWithMergedRegions() {
Workbook wb = getTestDataProvider().createWorkbook();
Sheet sheet = wb.createSheet();
Row row = sheet.createRow(0);
row.createCell(0).setCellValue(1.1);
row.createCell(1).setCellValue(2.2);
CellRangeAddress region = new CellRangeAddress(0, 0, 0, 2);
assertEquals("A1:C1", region.formatAsString());
sheet.addMergedRegion(region);
sheet.shiftRows(0, 1, 2);
region = sheet.getMergedRegion(0);
assertEquals("A3:C3", region.formatAsString());
}
/**
* See bug #34023
*