bug 58557: fix from Alessandro Guarascio, shift hyperlinks when shifting rows on an XSSFSheet
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1711185 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
1d1bdc1521
commit
1396738460
@ -23,6 +23,7 @@ import org.apache.poi.openxml4j.opc.PackagePart;
|
|||||||
import org.apache.poi.openxml4j.opc.PackageRelationship;
|
import org.apache.poi.openxml4j.opc.PackageRelationship;
|
||||||
import org.apache.poi.ss.usermodel.Hyperlink;
|
import org.apache.poi.ss.usermodel.Hyperlink;
|
||||||
import org.apache.poi.ss.util.CellReference;
|
import org.apache.poi.ss.util.CellReference;
|
||||||
|
import org.apache.poi.util.Internal;
|
||||||
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTHyperlink;
|
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTHyperlink;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,8 +34,8 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTHyperlink;
|
|||||||
public class XSSFHyperlink implements Hyperlink {
|
public class XSSFHyperlink implements Hyperlink {
|
||||||
private int _type;
|
private int _type;
|
||||||
private PackageRelationship _externalRel;
|
private PackageRelationship _externalRel;
|
||||||
private CTHyperlink _ctHyperlink;
|
private CTHyperlink _ctHyperlink; //contains a reference to the cell where the hyperlink is anchored, getRef()
|
||||||
private String _location;
|
private String _location; //what the hyperlink refers to
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new XSSFHyperlink. This method is protected to be used only by XSSFCreationHelper
|
* Create a new XSSFHyperlink. This method is protected to be used only by XSSFCreationHelper
|
||||||
@ -94,6 +95,7 @@ public class XSSFHyperlink implements Hyperlink {
|
|||||||
/**
|
/**
|
||||||
* @return the underlying CTHyperlink object
|
* @return the underlying CTHyperlink object
|
||||||
*/
|
*/
|
||||||
|
@Internal
|
||||||
public CTHyperlink getCTHyperlink() {
|
public CTHyperlink getCTHyperlink() {
|
||||||
return _ctHyperlink;
|
return _ctHyperlink;
|
||||||
}
|
}
|
||||||
@ -219,7 +221,8 @@ public class XSSFHyperlink implements Hyperlink {
|
|||||||
/**
|
/**
|
||||||
* Assigns this hyperlink to the given cell reference
|
* Assigns this hyperlink to the given cell reference
|
||||||
*/
|
*/
|
||||||
protected void setCellReference(String ref) {
|
@Internal
|
||||||
|
public void setCellReference(String ref) {
|
||||||
_ctHyperlink.setRef(ref);
|
_ctHyperlink.setRef(ref);
|
||||||
}
|
}
|
||||||
protected void setCellReference(CellReference ref) {
|
protected void setCellReference(CellReference ref) {
|
||||||
|
@ -25,6 +25,7 @@ import java.io.InputStream;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -669,6 +670,13 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
|||||||
vml == null ? null : vml.findCommentShape(row, column));
|
vml == null ? null : vml.findCommentShape(row, column));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a Hyperlink in this sheet anchored at row, column
|
||||||
|
*
|
||||||
|
* @param row
|
||||||
|
* @param column
|
||||||
|
* @return hyperlink if there is a hyperlink anchored at row, column; otherwise returns null
|
||||||
|
*/
|
||||||
public XSSFHyperlink getHyperlink(int row, int column) {
|
public XSSFHyperlink getHyperlink(int row, int column) {
|
||||||
String ref = new CellReference(row, column).formatAsString();
|
String ref = new CellReference(row, column).formatAsString();
|
||||||
for(XSSFHyperlink hyperlink : hyperlinks) {
|
for(XSSFHyperlink hyperlink : hyperlinks) {
|
||||||
@ -679,6 +687,15 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of Hyperlinks in this sheet
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<XSSFHyperlink> getHyperlinkList() {
|
||||||
|
return Collections.unmodifiableList(hyperlinks);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private int[] getBreaks(CTPageBreak ctPageBreak) {
|
private int[] getBreaks(CTPageBreak ctPageBreak) {
|
||||||
CTBreak[] brkArray = ctPageBreak.getBrkArray();
|
CTBreak[] brkArray = ctPageBreak.getBrkArray();
|
||||||
@ -2610,6 +2627,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
|||||||
// remove row from _rows
|
// remove row from _rows
|
||||||
it.remove();
|
it.remove();
|
||||||
|
|
||||||
|
// FIXME: (performance optimization) this should be moved outside the for-loop so that comments only needs to be iterated over once.
|
||||||
// also remove any comments associated with this row
|
// also remove any comments associated with this row
|
||||||
if(sheetComments != null){
|
if(sheetComments != null){
|
||||||
CTCommentList lst = sheetComments.getCTComments().getCommentList();
|
CTCommentList lst = sheetComments.getCTComments().getCommentList();
|
||||||
@ -2624,6 +2642,16 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// FIXME: (performance optimization) this should be moved outside the for-loop so that hyperlinks only needs to be iterated over once.
|
||||||
|
// also remove any hyperlinks associated with this row
|
||||||
|
if (hyperlinks != null) {
|
||||||
|
for (XSSFHyperlink link : new ArrayList<XSSFHyperlink>(hyperlinks)) {
|
||||||
|
CellReference ref = new CellReference(link.getCellRef());
|
||||||
|
if (ref.getRow() == rownum) {
|
||||||
|
hyperlinks.remove(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2707,6 +2735,7 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
|||||||
rowShifter.updateFormulas(shifter);
|
rowShifter.updateFormulas(shifter);
|
||||||
rowShifter.shiftMerged(startRow, endRow, n);
|
rowShifter.shiftMerged(startRow, endRow, n);
|
||||||
rowShifter.updateConditionalFormatting(shifter);
|
rowShifter.updateConditionalFormatting(shifter);
|
||||||
|
rowShifter.updateHyperlinks(shifter);
|
||||||
|
|
||||||
//rebuild the _rows map
|
//rebuild the _rows map
|
||||||
SortedMap<Integer, XSSFRow> map = new TreeMap<Integer, XSSFRow>();
|
SortedMap<Integer, XSSFRow> map = new TreeMap<Integer, XSSFRow>();
|
||||||
@ -2902,6 +2931,9 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet {
|
|||||||
*/
|
*/
|
||||||
@Internal
|
@Internal
|
||||||
public void removeHyperlink(int row, int column) {
|
public void removeHyperlink(int row, int column) {
|
||||||
|
// CTHyperlinks is regenerated from scratch when writing out the spreadsheet
|
||||||
|
// so don't worry about maintaining hyperlinks and CTHyperlinks in parallel.
|
||||||
|
// only maintain hyperlinks
|
||||||
String ref = new CellReference(row, column).formatAsString();
|
String ref = new CellReference(row, column).formatAsString();
|
||||||
for (Iterator<XSSFHyperlink> it = hyperlinks.iterator(); it.hasNext();) {
|
for (Iterator<XSSFHyperlink> it = hyperlinks.iterator(); it.hasNext();) {
|
||||||
XSSFHyperlink hyperlink = it.next();
|
XSSFHyperlink hyperlink = it.next();
|
||||||
|
@ -22,6 +22,7 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.poi.common.usermodel.Hyperlink;
|
||||||
import org.apache.poi.ss.formula.FormulaParseException;
|
import org.apache.poi.ss.formula.FormulaParseException;
|
||||||
import org.apache.poi.ss.formula.FormulaParser;
|
import org.apache.poi.ss.formula.FormulaParser;
|
||||||
import org.apache.poi.ss.formula.FormulaRenderer;
|
import org.apache.poi.ss.formula.FormulaRenderer;
|
||||||
@ -38,6 +39,7 @@ import org.apache.poi.util.POILogFactory;
|
|||||||
import org.apache.poi.util.POILogger;
|
import org.apache.poi.util.POILogger;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFCell;
|
import org.apache.poi.xssf.usermodel.XSSFCell;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
|
import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFHyperlink;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFName;
|
import org.apache.poi.xssf.usermodel.XSSFName;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFRow;
|
import org.apache.poi.xssf.usermodel.XSSFRow;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||||
@ -281,6 +283,30 @@ public final class XSSFRowShifter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shift the Hyperlink anchors (not the hyperlink text, even if the hyperlink
|
||||||
|
* is of type LINK_DOCUMENT and refers to a cell that was shifted). Hyperlinks
|
||||||
|
* do not track the content they point to.
|
||||||
|
*
|
||||||
|
* @param shifter
|
||||||
|
*/
|
||||||
|
public void updateHyperlinks(FormulaShifter shifter) {
|
||||||
|
int sheetIndex = sheet.getWorkbook().getSheetIndex(sheet);
|
||||||
|
List<XSSFHyperlink> hyperlinkList = sheet.getHyperlinkList();
|
||||||
|
|
||||||
|
for (XSSFHyperlink hyperlink : hyperlinkList) {
|
||||||
|
String cellRef = hyperlink.getCellRef();
|
||||||
|
CellRangeAddress cra = CellRangeAddress.valueOf(cellRef);
|
||||||
|
CellRangeAddress shiftedRange = shiftRange(shifter, cra, sheetIndex);
|
||||||
|
if (shiftedRange != null && shiftedRange != cra) {
|
||||||
|
// shiftedRange should not be null. If shiftedRange is null, that means
|
||||||
|
// that a hyperlink wasn't deleted at the beginning of shiftRows when
|
||||||
|
// identifying rows that should be removed because they will be overwritten
|
||||||
|
hyperlink.setCellReference(shiftedRange.formatAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static CellRangeAddress shiftRange(FormulaShifter shifter, CellRangeAddress cra, int currentExternSheetIx) {
|
private static CellRangeAddress shiftRange(FormulaShifter shifter, CellRangeAddress cra, int currentExternSheetIx) {
|
||||||
// FormulaShifter works well in terms of Ptgs - so convert CellRangeAddress to AreaPtg (and back) here
|
// FormulaShifter works well in terms of Ptgs - so convert CellRangeAddress to AreaPtg (and back) here
|
||||||
AreaPtg aptg = new AreaPtg(cra.getFirstRow(), cra.getLastRow(), cra.getFirstColumn(), cra.getLastColumn(), false, false, false, false);
|
AreaPtg aptg = new AreaPtg(cra.getFirstRow(), cra.getLastRow(), cra.getFirstColumn(), cra.getLastColumn(), false, false, false, false);
|
||||||
|
@ -21,7 +21,12 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import org.apache.poi.ss.usermodel.BaseTestSheetShiftRows;
|
import org.apache.poi.ss.usermodel.BaseTestSheetShiftRows;
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
import org.apache.poi.ss.usermodel.CellStyle;
|
||||||
import org.apache.poi.ss.usermodel.Comment;
|
import org.apache.poi.ss.usermodel.Comment;
|
||||||
|
import org.apache.poi.ss.usermodel.CreationHelper;
|
||||||
|
import org.apache.poi.ss.usermodel.Font;
|
||||||
|
import org.apache.poi.ss.usermodel.Hyperlink;
|
||||||
|
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||||
import org.apache.poi.ss.usermodel.Row;
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
import org.apache.poi.ss.usermodel.Sheet;
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
import org.apache.poi.ss.usermodel.Workbook;
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
@ -366,4 +371,105 @@ public final class TestXSSFSheetShiftRows extends BaseTestSheetShiftRows {
|
|||||||
|
|
||||||
wb.close();
|
wb.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testBug46742_shiftHyperlinks() throws IOException {
|
||||||
|
XSSFWorkbook wb = new XSSFWorkbook();
|
||||||
|
Sheet sheet = wb.createSheet("test");
|
||||||
|
Row row = sheet.createRow(0);
|
||||||
|
|
||||||
|
// How to create hyperlinks
|
||||||
|
// https://poi.apache.org/spreadsheet/quick-guide.html#Hyperlinks
|
||||||
|
XSSFCreationHelper helper = wb.getCreationHelper();
|
||||||
|
CellStyle hlinkStyle = wb.createCellStyle();
|
||||||
|
Font hlinkFont = wb.createFont();
|
||||||
|
hlinkFont.setUnderline(Font.U_SINGLE);
|
||||||
|
hlinkFont.setColor(IndexedColors.BLUE.getIndex());
|
||||||
|
hlinkStyle.setFont(hlinkFont);
|
||||||
|
|
||||||
|
// 3D relative document link
|
||||||
|
Cell cell = row.createCell(0);
|
||||||
|
cell.setCellStyle(hlinkStyle);
|
||||||
|
createHyperlink(helper, cell, Hyperlink.LINK_DOCUMENT, "test!E1");
|
||||||
|
|
||||||
|
// URL
|
||||||
|
cell = row.createCell(1);
|
||||||
|
cell.setCellStyle(hlinkStyle);
|
||||||
|
createHyperlink(helper, cell, Hyperlink.LINK_URL, "http://poi.apache.org/");
|
||||||
|
|
||||||
|
// row0 will be shifted on top of row1, so this URL should be removed from the workbook
|
||||||
|
Row overwrittenRow = sheet.createRow(3);
|
||||||
|
cell = overwrittenRow.createCell(2);
|
||||||
|
cell.setCellStyle(hlinkStyle);
|
||||||
|
createHyperlink(helper, cell, Hyperlink.LINK_EMAIL, "mailto:poi@apache.org");
|
||||||
|
|
||||||
|
// hyperlinks on this row are unaffected by the row shifting, so the hyperlinks should not move
|
||||||
|
Row unaffectedRow = sheet.createRow(20);
|
||||||
|
cell = unaffectedRow.createCell(3);
|
||||||
|
cell.setCellStyle(hlinkStyle);
|
||||||
|
createHyperlink(helper, cell, Hyperlink.LINK_FILE, "54524.xlsx");
|
||||||
|
|
||||||
|
cell = wb.createSheet("other").createRow(0).createCell(0);
|
||||||
|
cell.setCellStyle(hlinkStyle);
|
||||||
|
createHyperlink(helper, cell, Hyperlink.LINK_URL, "http://apache.org/");
|
||||||
|
|
||||||
|
int startRow = 0;
|
||||||
|
int endRow = 0;
|
||||||
|
int n = 3;
|
||||||
|
sheet.shiftRows(startRow, endRow, n);
|
||||||
|
|
||||||
|
XSSFWorkbook read = XSSFTestDataSamples.writeOutAndReadBack(wb);
|
||||||
|
wb.close();
|
||||||
|
|
||||||
|
XSSFSheet sh = read.getSheet("test");
|
||||||
|
|
||||||
|
Row shiftedRow = sh.getRow(3);
|
||||||
|
|
||||||
|
// document link anchored on a shifted cell should be moved
|
||||||
|
// Note that hyperlinks do not track what they point to, so this hyperlink should still refer to test!E1
|
||||||
|
verifyHyperlink(shiftedRow.getCell(0), Hyperlink.LINK_DOCUMENT, "test!E1");
|
||||||
|
|
||||||
|
// URL, EMAIL, and FILE links anchored on a shifted cell should be moved
|
||||||
|
verifyHyperlink(shiftedRow.getCell(1), Hyperlink.LINK_URL, "http://poi.apache.org/");
|
||||||
|
|
||||||
|
// Make sure hyperlinks were moved and not copied
|
||||||
|
assertNull(sh.getRow(0));
|
||||||
|
|
||||||
|
// Make sure hyperlink in overwritten row is deleted
|
||||||
|
assertEquals(3, sh.getHyperlinkList().size());
|
||||||
|
for (XSSFHyperlink link : sh.getHyperlinkList()) {
|
||||||
|
if ("C4".equals(link.getCellRef())) {
|
||||||
|
fail("Row 4, including the hyperlink at C4, should have been deleted when Row 1 was shifted on top of it.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure unaffected rows are not shifted
|
||||||
|
Cell unaffectedCell = sh.getRow(20).getCell(3);
|
||||||
|
assertTrue(cellHasHyperlink(unaffectedCell));
|
||||||
|
verifyHyperlink(unaffectedCell, Hyperlink.LINK_FILE, "54524.xlsx");
|
||||||
|
|
||||||
|
// Make sure cells on other sheets are not affected
|
||||||
|
unaffectedCell = read.getSheet("other").getRow(0).getCell(0);
|
||||||
|
assertTrue(cellHasHyperlink(unaffectedCell));
|
||||||
|
verifyHyperlink(unaffectedCell, Hyperlink.LINK_URL, "http://apache.org/");
|
||||||
|
|
||||||
|
read.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createHyperlink(CreationHelper helper, Cell cell, int linkType, String ref) {
|
||||||
|
cell.setCellValue(ref);
|
||||||
|
Hyperlink link = helper.createHyperlink(linkType);
|
||||||
|
link.setAddress(ref);
|
||||||
|
cell.setHyperlink(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyHyperlink(Cell cell, int linkType, String ref) {
|
||||||
|
assertTrue(cellHasHyperlink(cell));
|
||||||
|
Hyperlink link = cell.getHyperlink();
|
||||||
|
assertEquals(linkType, link.getType());
|
||||||
|
assertEquals(ref, link.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean cellHasHyperlink(Cell cell) {
|
||||||
|
return (cell != null) && (cell.getHyperlink() != null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user