Is it possible to set the active range with Apache POI XSSF? - java

I am using Apache POI XSSF to read and write Excel Sheets.
I know that I can set the active cell on a worksheet by using Sheet.setActiveCell(CellAddress address).
However, I'd like to set it to a Range containing more than one cell on the sheet, as illustrated by the picture below:
When I save a sheet with multiple cells selected using Excel those are selected upon opening the saved file.
Is there a way to do this with POI XSSF?

you can use following line to achieve a ranke as active cell in excel:
sheet.setActiveCell("A1:B2");
Hope it helps.
As from 3.16 onwards the setActiveCell(String) method is deprecated and you do not want to use a deprecated method I would suggest to create your own CellAddress:
public class CellRangeAddress extends CellAddress {
private CellAddress start;
private CellAddress end;
public CellRangeAddress(final CellAddress start, final CellAddress end) {
super(start);
this.start = start;
this.end = end;
}
#Override
public String formatAsString() {
if (end != null) {
return start.formatAsString() + ":" + end.formatAsString();
}
return super.formatAsString();
}
}
and use ist like:
sheet.setActiveCell(new CellRangeAddress(new CellAddress("A1"), new CellAddress("B2")));
Not the cleanest and best way, but works without warnings.

Related

Trim sheet name in Sheet interface (Apache POI)

I'm trying to get Sheet by using:
Sheet sheet=workbook.getSheet("sheetName");
But in my workbook, some sheet names has whitespaces. How to make "trim search"? Is is possible to do something like this:
Sheet sheet=workbook.trimSheetSearch("sheetName");
Or
Sheet sheet=workbook.indexOfSheetSearch("sheetName");
You should be able to iterate each Sheet to improve your search with Workbook.getNumberOfSheets() and Workbook.getSheetAt(int index).
Something like :
for(int i = 0; i < workbook.getNumberOfSheets(); i++){
String s = workbook.getSheetName(i);
if(checkName(s, nameToFind)) return workbook.getSheetAt(i);
}
return null; // sheet not found
For the search method, you can do a dirty solution like
public boolean checkName(String n1, String n2){
return n1.replaceAll(" ", "").equalsIgnoreCase(n2.replaceAll(" ", ""));
}
I let you improve that method (I will not do everything here ;) ). Note that this might not compile because I don't have an actual system to run this but this should work just fine.

How can Apache POI use formulas in streaming mode?

I am using Apache POI 3.17 (current). When I use HSSFCell.setFormula() to insert a formula like "A1+17" it works. When I do the same in streaming mode, using SXSSFCell.setFormula() the formula appears (with a leading "=") in the input line but the displayed result in the cell is always 0.
I tried with the cell types NUMERIC and FORMULA. Here is my minimal not working example:
final SXSSFWorkbook wb = new SXSSFWorkbook();
final SXSSFSheet sheet = wb.createSheet("Test-S");
final SXSSFRow row = sheet.createRow(0);
final SXSSFCell cell1 = row.createCell(0);
cell1.setCellType(CellType.NUMERIC);
cell1.setCellValue(124);
final SXSSFCell formulaCell1 = row.createCell(1);
formulaCell1.setCellType(CellType.FORMULA);
formulaCell1.setCellFormula("A1 + 17");
final SXSSFCell formulaCell2 = row.createCell(2);
formulaCell2.setCellType(CellType.NUMERIC);
formulaCell2.setCellFormula("A1+18");
FileOutputStream os = new FileOutputStream("/tmp/test-s.xlsx");
wb.write(os);
wb.close();
os.close();
The three cells display as 124/0/0, although in the input line the formulae are displayed correctly.
Any hints are appreciated.
It works for me with Excel 2016, I get the correct results in the cells when I open the sample file. Probably older versions of Excel handle this slightly differently, please try to force evaluation of formulas with the following two things
// evaluate all formulas and store cached results
wb.getCreationHelper().createFormulaEvaluator().evaluateAll();
// suggest to Excel to recalculate the formulas itself as well
sheet.setForceFormulaRecalculation(true);
Hopefully one of those two will make it work for you as well.
The answers does not answer the question why this problem with OpenOffice/Libreoffice only occurs if SXSSFCell is used as a formula cell. When using XSSFCell as a formula cell it does not occur.
The answer is that SXSSFCell always uses a cell value, even if the formula was not evaluated at all. And the worst thing is that it uses the value 0 (zero) if if the formula was not evaluated at all. This is a fundamental misusing of the value 0 in mathematics. The value 0 explicitly does not mean that there is not a value or that there is a unknown value. It means that there is the value 0 and nothing else. So the value 0 should not be used as the cached formula result of a not evaluated formula. Instead no value should be used until the formula is evaluated. Exact as XSSFCell does.
So the really correct answer must be that apache poi should correct their SXSSFCell code.
Workaround until this:
import java.io.FileOutputStream;
import org.apache.poi.xssf.streaming.*;
import org.apache.poi.ss.usermodel.CellType;
import java.lang.reflect.Field;
import java.util.TreeMap;
public class CreateExcelSXSSFFormula {
public static void main(String[] args) throws Exception {
SXSSFWorkbook wb = new SXSSFWorkbook();
SXSSFSheet sheet = wb.createSheet("Test-S");
SXSSFRow row = sheet.createRow(0);
SXSSFCell cell = row.createCell(0);
cell.setCellValue(124);
SXSSFFormulaonlyCell formulacell = new SXSSFFormulaonlyCell(row, 1);
formulacell.setCellFormula("A1+17");
cell = row.createCell(2);
cell.setCellFormula("A1+17");
formulacell = new SXSSFFormulaonlyCell(row, 3);
formulacell.setCellFormula("A1+18");
cell = row.createCell(4);
cell.setCellFormula("A1+18");
wb.write(new FileOutputStream("test-s.xlsx"));
wb.close();
wb.dispose();
}
private static class SXSSFFormulaonlyCell extends SXSSFCell {
SXSSFFormulaonlyCell(SXSSFRow row, int cellidx) throws Exception {
super(row, CellType.BLANK);
Field _cells = SXSSFRow.class.getDeclaredField("_cells");
_cells.setAccessible(true);
#SuppressWarnings("unchecked") //we know the problem and expect runtime error if it possibly occurs
TreeMap<Integer, SXSSFCell> cells = (TreeMap<Integer, SXSSFCell>)_cells.get(row);
cells.put(cellidx, this);
}
#Override
public CellType getCachedFormulaResultTypeEnum() {
return CellType.BLANK;
}
}
}
Of course I should have mentioned that I use LibreOffice. I have now found that LibreOffice intentionally does not recalculate formulae from an Excel-created sheet, and it considers POI sheets as Excel-created.
See https://ask.libreoffice.org/en/question/12165/calc-auto-recalc-does-not-work/ .
Changing the LibreOffice settings (Tools – Options – LibreOffice Calc – formula – Recalculation on file load) helps.

How to edit a Hyperlink in a Word Document using Apache POI?

So I've been browsing around the source code / documentation for POI (specifically XWPF) and I can't seem to find anything that relates to editing a hyperlink in a .docx. I only see functionality to get the information for the currently set hyperlink. My goal is to change the hyperlink in a .docx to link to "http://yahoo.com" from "http://google.com" as an example. Any help would be greatly appreciated. Thanks!
I found a way to edit the url of the link in a "indirect way" (copy the previous hyperlink, modify the url, delete the previous hyperlink and add the new one in the paragraph).
Code is shown below:
private void editLinksOfParagraph(XWPFParagraph paragraph, XWPFDocument document) {
for (int rIndex = 0; rIndex < paragraph.getRuns().size(); rIndex++) {
XWPFRun run = paragraph.getRuns().get(rIndex);
if (run instanceof XWPFHyperlinkRun) {
// get the url of the link to edit it
XWPFHyperlink link = ((XWPFHyperlinkRun) run).getHyperlink(document);
String linkURL = link.getURL();
//get the xml representation of the hyperlink that includes all the information
XmlObject xmlObject = run.getCTR().copy();
linkURL += "-edited-link"; //edited url of the link, f.e add a '-edited-link' suffix
//remove the previous link from the paragraph
paragraph.removeRun(rIndex);
//add the new hyperlinked with updated url in the paragraph, in place of the previous deleted
XWPFHyperlinkRun hyperlinkRun = paragraph.insertNewHyperlinkRun(rIndex, linkURL);
hyperlinkRun.getCTR().set(xmlObject);
}
}
}
This requirement needs knowledge about how hyperlinks referring to an external reference get stored in Microsoft Word documents and how this gets represented in XWPF of Apache POI.
The XWPFHyperlinkRun is the representation of a linked text run in a IRunBody. This text run, or even multiple text runs, is/are wrapped with a XML object of type CTHyperlink. This contains a relation ID which points to a relation in the package relations part. This package relation contains the URI which is the hyperlink's target.
Currently (apache poi 5.2.2) XWPFHyperlinkRun provides access to a XWPFHyperlink. But this is very rudimentary. It only has getters for the Id and the URI. It neither provides access to it's XWPFHyperlinkRun and it's IRunBody nor it provides a setter for the target URI in the package relations part. It not even has internally access to it's the package relations part.
So only using Apache POI classes the only possibility currently is to delete the old XWPFHyperlinkRun and create a new one pointing to the new URI. But as the text runs also contain the text formatting, deleting them will also delete the text formatting. It would must be copied from the old XWPFHyperlinkRun to the new before deleting the old one. That's uncomfortable.
So the rudimentary XWPFHyperlink should be extended to provide a setter for the target URI in the package relations part. A new class XWPFHyperlinkExtended could look like so:
import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
/**
* Extended XWPF hyperlink class
* Provides access to it's Id, URI, XWPFHyperlinkRun, IRunBody.
* Provides setting target URI in PackageRelationship.
*/
public class XWPFHyperlinkExtended {
private String id;
private String uri;
private XWPFHyperlinkRun hyperlinkRun;
private IRunBody runBody;
private PackageRelationship rel;
public XWPFHyperlinkExtended(XWPFHyperlinkRun hyperlinkRun, PackageRelationship rel) {
this.id = rel.getId();
this.uri = rel.getTargetURI().toString();
this.hyperlinkRun = hyperlinkRun;
this.runBody = hyperlinkRun.getParent();
this.rel = rel;
}
public String getId() {
return this.id;
}
public String getURI() {
return this.uri;
}
public IRunBody getIRunBody() {
return this.runBody;
}
public XWPFHyperlinkRun getHyperlinkRun() {
return this.hyperlinkRun;
}
/**
* Provides setting target URI in PackageRelationship.
* The old PackageRelationship gets removed.
* A new PackageRelationship gets added using the same Id.
*/
public void setTargetURI(String uri) {
this.runBody.getPart().getPackagePart().removeRelationship(this.getId());
this.uri = uri;
PackageRelationship rel = this.runBody.getPart().getPackagePart().addExternalRelationship(uri, XWPFRelation.HYPERLINK.getRelation(), this.getId());
this.rel = rel;
}
}
It does not extend XWPFHyperlink as this is so rudimentary it's not worth it. Furthermore after setTargetURI the String uri needs to be updated. But there is no setter in XWPFHyperlink and the field is only accessible from inside the package.
The new XWPFHyperlinkExtended can be got from XWPFHyperlinkRun like so:
/**
* If this HyperlinkRun refers to an external reference hyperlink,
* return the XWPFHyperlinkExtended object for it.
* May return null if no PackageRelationship found.
*/
/*modifiers*/ XWPFHyperlinkExtended getHyperlink(XWPFHyperlinkRun hyperlinkRun) {
try {
for (org.apache.poi.openxml4j.opc.PackageRelationship rel : hyperlinkRun.getParent().getPart().getPackagePart().getRelationshipsByType(XWPFRelation.HYPERLINK.getRelation())) {
if (rel.getId().equals(hyperlinkRun.getHyperlinkId())) {
return new XWPFHyperlinkExtended(hyperlinkRun, rel);
}
}
} catch (org.apache.poi.openxml4j.exceptions.InvalidFormatException ifex) {
// do nothing, simply do not return something
}
return null;
}
Once we have that XWPFHyperlinkExtended we can set an new target URI using it's method setTargetURI.
A further problem results from the fact, that the XML object of type CTHyperlink can wrap around multiple text runs, not only one. Then multiple XWPFHyperlinkRun are in one CTHyperlink and point to one target URI. For example this could look like:
... [this is a link to example.com]->https://example.com ...
This results in 6 XWPFHyperlinkRuns in one CTHyperlink linking to https://example.com.
This leads to problems when link text needs to be changed when the link target changes. The text of all the 6 text runs is the link text. So which text run shall be changed?
The best I have found is a method which sets the text of the first text run in the CTHyperlink.
/**
* Sets the text of the first text run in the CTHyperlink of this XWPFHyperlinkRun.
* Tries solving the problem when a CTHyperlink contains multiple text runs.
* Then the String value is set in first text run only. All other text runs are set empty.
*/
/*modifiers*/ void setTextInFirstRun(XWPFHyperlinkRun hyperlinkRun, String value) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHyperlink ctHyperlink = hyperlinkRun.getCTHyperlink();
for (int r = 0; r < ctHyperlink.getRList().size(); r++) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR ctR = ctHyperlink.getRList().get(r);
for (int t = 0; t < ctR.getTList().size(); t++) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText ctText = ctR.getTList().get(t);
if (r == 0 && t == 0) {
ctText.setStringValue(value);
} else {
ctText.setStringValue("");
}
}
}
}
There the String value is set in first text run only. All other text runs are set empty. The text formatting of the first text run remains.
This works, but need more some steps to get text formatting correctly:
try (var fis = new FileInputStream(fileName);
var doc = new XWPFDocument(fis)) {
var pList = doc.getParagraphs();
for (var p : pList) {
var runs = p.getRuns();
for (int i = 0; i < runs.size(); i++) {
var r = runs.get(i);
if (r instanceof XWPFHyperlinkRun) {
var run = (XWPFHyperlinkRun) r;
var link = run.getHyperlink(doc);
// To get text: link for checking
System.out.println(run.getText(0) + ": " + link.getURL());
// how i replace it
var run1 = p.insertNewHyperlinkRun(i, "http://google.com");
run1.setText(run.getText(0));
// remove the old link
p.removeRun(i + 1);
}
}
}
try (var fos = new FileOutputStream(outFileName)) {
doc.write(fos);
}
}
I'm using these libraries:
implementation 'org.apache.poi:poi:5.2.2'
implementation 'org.apache.poi:poi-ooxml:5.2.2'

Accessing Field Settings of Pivot Table using Apache POI

I am creating a workbook with a sheet populated data from a data source then creating a second sheet with a pivot table view of that data. Everything works fine, but I can't seem to change the default look of the pivot table. I am trying to get the setting ( Row Labels-->Click one from the list-->Field Settings-->Subtotals-->None and Row Labels-->Click one from the list-->Field Settings-->Layout & Print-->'Show item labels in tabular form' ) checked while creating the pivot table but couldn't find the handle / flag in the POI. Tried finding something under pivotTable.getCTPivotTableDefinition() or pivotTable.getCTPivotTableDefinition().getPivotTableStyleInfo(), but no lock. Please advise if there is a way to set these settings using poi during pivot table creation, not after the fact following the steps mentioned in the parenthesis. Here is my pivot table code :
XSSFSheet sheet = (XSSFSheet)wb.createSheet("Data");
...
...
//filling data sheet, skipping this part as it's not relevant
...
XSSFSheet pivotSheet = (XSSFSheet)wb.createSheet("Pivot Table");
AreaReference source = new AreaReference(sheet.getSheetName()+"!A$1:W$"+String.valueOf(sheet.getLastRowNum()));
CellReference position = new CellReference("A3");
XSSFPivotTable pivotTable = pivotSheet.createPivotTable(source, position);
/* Add filters */
pivotTable.addRowLabel(17);
pivotTable.addRowLabel(20);
pivotTable.addRowLabel(21);
pivotTable.addRowLabel(22);
pivotTable.addRowLabel(13);
pivotTable.addRowLabel(19);
pivotTable.addRowLabel(6);
pivotTable.addRowLabel(0);
pivotTable.addRowLabel(18);
pivotTable.addRowLabel(1);
pivotTable.addRowLabel(7);
pivotTable.addRowLabel(9);
Finally figured it out; lack of good documentation forced me to try a zillion things and finally was able to achieve what I wanted; here is the code :
for(CTPivotField ctPivotField:pivotTable.getCTPivotTableDefinition().getPivotFields().getPivotFieldList()){
ctPivotField.setAutoShow(false);
ctPivotField.setOutline(false);
ctPivotField.setSubtotalTop(false);
ctPivotField.setSubtotalCaption("");
}
instead of creating the pivot table every time, I created one template XLS file with all the desired styling and included that in the source, now I am opening that file filling the necessary data in the source tab, and saving the XLS file with the dynamic data with a different name; since the Pivot table tab is marked to refresh when opened, it does the work. Instead of going through the POI API with the limitations on Pivot Tables, creating a template and using it is much easier and flexible if you will generate the same styled pivot table for dynamic data.
#ninjaxelite here how it goes :
List<Object[]> resultSet = //get raw data
XSSFWorkbook wb = null;
try {
wb = new XSSFWorkbook(new FileInputStream(this.getClass().getResource("/content/XLS_template.xlsx").getPath()));
} catch (FileNotFoundException e1) {
//error
} catch (IOException e1) {
//error
}
Map<String, CellStyle> styles = createStyles(wb); // some local function to get styles
XSSFSheet sheet = (XSSFSheet)wb.getSheetAt(0);
XSSFRow row;
XSSFCell cell;
int rowNum = 0;
for (Object[] aRow : resultSet) {
rowNum++;
row = sheet.createRow(rowNum);
cell = row.createCell(0);
cell.setCellValue((String)aRow[0]);
cell.setCellStyle(styles.get("cell_normal_centered"));
...
..
.

Refresh Pivot table using Apache POI

No/Minimal documentation regarding Apache POI for Pivot tables in the Apache site has got me to write this.
I want to refresh a pivot table in a Work Book using Apache POI.
Please let me know where I can get proper documentation and Examples regarding this.
Kindly follow the followings which i did.
Fill rough data for pivot table in your MyFileName.xlsx file.
Create a Dynamic Range Formula through OFFSET() or Named Table as Source Data for Pivot table and draw pivot table.
Simply Right click your pivot table and choose
pivotTable Options->Data-> Check Refresh Data when opening File
Open a MyFileName.xlsx file and fill out the data.
That's all...
whenever you opening workbook it will be refreshed to current data. :-)
Note: This will not work when you creating Pivot table through POI.
The link codeMan refers to has some advice that looks quite specific to Apache POI & Excel.
You'll see that there is no great documentation for a reason here (it's not supported):
http://poi.apache.org/spreadsheet/limitations.html
To quote Solitudes answer in codeMans link verbatim:
It is possible. In the PivotCacheDefinition, there is an attribute
refreshOnLoad that can be set to true. The cache is then refreshed
when the workbook is opened. More information here.
> In POI this can be done by calling the method setRefreshOnLoad(boolean
bool), that takes a boolean as parameter, on a CTPivotCacheDefinition
If you need to refresh the pivot table before the file is opened, (for example to then use the pivottable calculated data in further calculations and have POI write this) then I'm not sure that this would be possible at all with POI, and potentially hooking up to excel using a COM solution might be the way to go.
Apart from the limitations, you can check a little information about Package org.apache.poi.hssf.record.pivottable
Though if I will have to do the same, I would create the table/chart manually once and will update the chart using apache poi as I have done here
another solution (WO VBA scripts)
In tempalate.xlsx create xlTable object on headers of source record set. Set name to the xlTable, ex. 'mySourceTable'.
Also in file preSet for your PivotTable:
sourceRef ='mySourceTable'
check in RefreshOnLoad
In POI:
private void updateXlTableSource() {
XSSFTable sourceTable = ((XSSFWorkbook)workbook).getTable("mySourceTable");
CTTable ctTable = sourceTable.getCTTable();
String sourceRef = getSourceDataRange().formatAsString();
ctTable.setRef(sourceRef);
ctTable.getAutoFilter().setRef(sourceRef);
}
private CellRangeAddress getSourceDataRange() {
XSSFSheet xssfSheet = (XSSFSheet) workbook.getSheet("sourceSheetName");
int uBoundSourceDataRow = findFirstEmptyRowFrom(xssfSheet) - 1;
if (uBoundSourceDataRow < 2) {
uBoundSourceDataRow = 2;
}
int uBoundSourceDataCol = findFirstEmptyColFromFirstRow(xssfSheet) - 1;
return new CellRangeAddress(0, uBoundSourceDataRow, 0, uBoundSourceDataCol);
}
Note: check your tempalate.xlsx for unKnown query. Delete if it exists, else it will block PT updating
Drawback: PT's autoFilter contains notexisting elements (elements from PT template).
save your file with PTs as file.xlsm and insert VBA script (ALT+F11):
' Create module and insert this:
Public Const pivotName1 As String = "myPivotName"
Public Const sourceSheetName As String = "source"
Public Const sourceColumnCount As Long = 23
' In "ThisWorkbook" chapter insert this:
Dim lRow As Long
Private Sub Workbook_Open()
Application.ScreenUpdating = False
ActiveWorkbook.Worksheets(sourceSheetName).Activate
' In file should preliminarily insert keyWord "firstOpenFlag" in CV1 cell (sheet sourceSheetName)
' It gona start actions below
If ActiveSheet.Cells(1, 100) = "firstOpenFlag"
Then
ActiveSheet.Cells(1, 100) = ""
lRow = getLastRowForFirstCol(sourceSheetName)
Call updateAllPTCache
ActiveWorkbook.Worksheets(sourceSheetName).Activate
ActiveSheet.Range("A1").Select
End If
Application.ScreenUpdating = True
End Sub
Private Function getLastRowForFirstCol(sourceSheetName As String) As Long
ActiveWorkbook.Worksheets(sourceSheetName).Activate
getLastRowForFirstCol = ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
If getLastRowForFirstCol < 2 Then getLastRowForFirstCol = 2
End Function
Private Sub updateAllPTCache()
Dim pt As PivotTable
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
For Each pt In ws.PivotTables
pt.ChangePivotCache ActiveWorkbook.PivotCaches.Create( _
SourceType:=xlDatabase, _
SourceData:=sourceSheetName + "!R1C1:R" + CStr(lRow) + "C" + CStr(sourceColumnCount), _
Version:=xlPivotTableVersion14)
' xlPivotTableVersion14 - work in 2013, 2016 exlApp
' Downgrade xlPivotTableVersion for backward compatibility
pt.RefreshTable
Next pt
Next ws
End Sub
Drawback: clients xlsApp should be configed to enable VBA scripts

Categories