I have this code and this error constantly is appearing. I have only one excel , but nothing seems to work, I already tried a lot of option that I found surfing on internet, but nothing seems to work according of what I want to do.
I use different case to make easier the logical of my business and I am not going to change that, so I am not sure how to do solve this issue.
private static final String nombreArchivo = "casoPrueba.xlsx";
private static final String rutaArchivo = "src\\test\\resources\\data\\" + nombreArchivo;
public static XSSFSheet SacaHojaSegunTipo(String tipo) throws IOException {
if (workbook == null) {
try (FileInputStream fis = new FileInputStream(new File(rutaArchivo))) {
workbook = new XSSFWorkbook(fis);
}
}
XSSFSheet spreadsheet = null;
switch (tipo) {
case "Candidatos Minorista":
spreadsheet = workbook.getSheetAt(1);
break;
case "Conversion Candidatos":
spreadsheet = workbook.getSheetAt(2);
break;
case "Cuentas":
spreadsheet = workbook.getSheetAt(3);
break;
case "Detalle Cuenta":
spreadsheet = workbook.getSheetAt(4);
break;
case "Historial de Cuentas":
spreadsheet = workbook.getSheetAt(5);
break;
case "Cuentas Financieras":
spreadsheet = workbook.getSheetAt(6);
break;
case "AR Estado Automático":
spreadsheet = workbook.getSheetAt(7);
break;
case "Oportunidades":
spreadsheet = workbook.getSheetAt(8);
break;
default:
spreadsheet = workbook.getSheetAt(0);
break;
}
return spreadsheet;
}
I know this is not a efficient method.Hope anyone can help me with this.
Something like this (I tried to change your code as little as possible, so it's not perfect)
private static final String nombreArchivo = "casoPrueba.xlsx";
private static final String rutaArchivo = "src\\test\\resources\\data\\" + nombreArchivo;
private static XSSFWorkbook workbook = null;
public static XSSFSheet SacaHojaSegunTipo(String tipo) throws IOException {
if (workbook == null) {
try (FileInputStream fis = new FileInputStream(new File(rutaArchivo))) {
workbook = new XSSFWorkbook(fis);
}
}
XSSFSheet spreadsheet = null;
switch (tipo) {
case "Candidatos Minorista":
spreadsheet = workbook.getSheetAt(1);
break;
case "Conversion Candidatos":
spreadsheet = workbook.getSheetAt(2);
break;
case "Cuentas":
spreadsheet = workbook.getSheetAt(3);
break;
case "Detalle Cuenta":
spreadsheet = workbook.getSheetAt(4);
break;
case "Historial de Cuentas":
spreadsheet = workbook.getSheetAt(5);
break;
case "Navegar Cuentas":
spreadsheet = workbook.getSheetAt(6);
break;
case "Validar Número Operación":
spreadsheet = workbook.getSheetAt(7);
break;
case "Validar Tipos de Productos":
spreadsheet = workbook.getSheetAt(8);
break;
case "Validar Referencia y Cód. Auto.":
spreadsheet = workbook.getSheetAt(9);
break;
default:
spreadsheet = workbook.getSheetAt(0);
}
return spreadsheet;
}
First, a quick aside : it's worth noting the following from
https://poi.apache.org/apidocs/dev/org/apache/poi/xssf/usermodel/XSSFWorkbook.html#XSSFWorkbook-java.io.InputStream-
Using an InputStream requires more memory than using a File, so if a
File is available then you should instead do something like
OPCPackage pkg = OPCPackage.open(path);
XSSFWorkbook wb = new XSSFWorkbook(pkg);
// work with the wb object
......
pkg.close(); // gracefully closes the underlying zip file
(although doing wb.close() also closes the files and streams).
Now, your core issue is that you need to release resources after the sheet or workbook are no longer required, but at present you cannot do so since these are hidden local inside the method.
So you need to give your caller access to close them when it's done. It's a matter of preference, but personally I would prefer encapsulating the spreadsheet into it's own class - a spreadsheet IS a clearly defined object in its own right, after all ! As such, this would necessitate a change from static, so something like :
public class RutaArchivo implements AutoCloseable {
private static final String nombreArchivo = "casoPrueba.xlsx";
private static final String rutaArchivo = "src\\test\\resources\\data\\" + nombreArchivo;
public static final String CANDIDATOS_MINORISTA = "Candidatos Minorista";
public static final String CONVERSION_CANDIDATOS = "Conversion Candidatos"
public static final String CUENTAS = "Cuentas";
private XSSFWorkbook workbook;
public RutaArchivo() throws InvalidFormatException, IOException {
workbook = new XSSFWorkbook(new File(rutaArchivo));
}
#Override
public void close() throws Exception {
if (workbook != null) {
workbook.close();
workbook = null;
}
}
public XSSFSheet sacaHojaSegunTipo(String tipo) {
if (workbook == null) {
throw new IllegalStateException("It's closed");
}
XSSFSheet spreadsheet = workbook.getSheetAt(0);
if (tipo .equals(CANDIDATOS_MINORISTA)) {
spreadsheet = workbook.getSheetAt(1);
}else if(tipo.equals(CONVERSION_CANDIDATOS)){
spreadsheet = workbook.getSheetAt(2);
}else if(tipo.equals(CUENTAS)){
spreadsheet = workbook.getSheetAt(3);
// etc, etc
}
return spreadsheet;
}
}
A couple of things to note :
If we want the caller to close the file, then we should explictly make them take some action to open it as well, otherwise it's too easy for it to be left hanging. In the example above, this is implicit in creating the object - just like the standard Java types like FileInputStream, etc.
Making RutaArchivo AutoCloseable means that it can used in try-with-resources, so closed automatically - eg :
try (RutaArchivo rutaArchivo = new RutaArchivo()) {
XSSFSheet cuentas = rutaArchivo.getSheet(RutaArchivo.CUENTAS);
}
Using constants for the names of the sheets reduces bugs (eg, no typos when the method is called)
As this is it's own class rather than static methods, it's easier to substitute or mock when writing unit tests.
Anyhow, a few thoughts - hope they help.
Related
I am trying data driven testing with two different sheets in one single excel sheet. But what I observed is same code gave two different results when I tried executing a two different test cases. It was strange. One sheet data got properly processed but for another, same code gave Null pointer exception on a particular line. Below is the code.
#DataProvider(name="testdataforinvalidvalues")
public static Object[][] readexcelretobjinvalidval() throws Exception {
System.out.println("Data provider called");
HSSFWorkbook workbook = assignworkbook();
HSSFSheet s = workbook.getSheetAt(2);
String sp = s.getSheetName();
System.out.println(sp);
HSSFRow row = s.getRow(0);
int rownum=s.getPhysicalNumberOfRows();
int colnum=row.getLastCellNum();
System.out.println(rownum);
System.out.println(colnum);
//System.out.println(rownum);
//System.out.println(colnum);
Object data[][] = new Object[rownum][colnum];
List<Object> l = new ArrayList<Object>();
for(int i=0;i<rownum-1;i++) {
row = s.getRow(i+1);
for(int j=0;j<colnum;j++) {
**HSSFCell c = row.getCell(j);**
//l.add(c.getStringCellValue());
data[i][j]=c.getStringCellValue();
}
}
return data;
The line in the code HSSFCell c = row.getCell(j); which I tried to show in bold just below for loop is throwing null pointer exception.
The test case which I tried to run is as follows.
#Test(dataProvider="testdataforinvalidvalues",dataProviderClass=Excel_Util.class)
public void testsignupdatafieldsinvval(String email, String password) {
Syncutil.Implicitwait();
System.out.println("Test case called");
try {
getdriver().get("https://www."+csvreaderutil.csvread().get(0).toString()+"/");
Marathishd marathishdpage = PageFactory.initElements(getdriver(), Marathishd.class);
Signuppagemarathishd signup = marathishdpage.letsbeginclick();
signup.emailid.sendKeys(email);
signup.password.sendKeys(password);
Syncutil.Explicitwait(signup.invalidemailerrormessage);
Syncutil.Explicitwait(signup.invalidpassworderrormessage);
Syncutil.Explicitwait(signup.enteremailmessage);
Assert.assertEquals(signup.enteremailmessage.getText(), "Enter your Email ID");
Assert.assertEquals(signup.invalidemailerrormessage.getText(), "Please enter correct Email");
Assert.assertEquals(signup.invalidpassworderrormessage.getText(), "Please enter 4 to 20 characters password without any spaces");
}catch(Exception e) {System.out.println("Exception");}
}
I am confused why the data provider code behaved differently for two different sheets.Can anyone please help me ?
I have a list of strings in read from MongoDB (~200k lines)
Then I want to write it to an excel file with Java code:
public class OutputToExcelUtils {
private static XSSFWorkbook workbook;
private static final String DATA_SEPARATOR = "!";
public static void clusterOutToExcel(List<String> data, String outputPath) {
workbook = new XSSFWorkbook();
FileOutputStream outputStream = null;
writeData(data, "Data");
try {
outputStream = new FileOutputStream(outputPath);
workbook.write(outputStream);
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void writeData(List<String> data, String sheetName) {
int rowNum = 0;
XSSFSheet sheet = workbook.getSheet(sheetName);
sheet = workbook.createSheet(sheetName);
for (int i = 0; i < data.size(); i++) {
System.out.println(sheetName + " Processing line: " + i);
int colNum = 0;
// Split into value of cell
String[] valuesOfLine = data.get(i).split(DATA_SEPERATOR);
Row row = sheet.createRow(rowNum++);
for (String valueOfCell : valuesOfLine) {
Cell cell = row.createCell(colNum++);
cell.setCellValue(valueOfCell);
}
}
}
}
Then I get an error:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead
limit exceeded at
org.apache.xmlbeans.impl.store.Cur$Locations.(Cur.java:497) at
org.apache.xmlbeans.impl.store.Locale.(Locale.java:168) at
org.apache.xmlbeans.impl.store.Locale.getLocale(Locale.java:242) at
org.apache.xmlbeans.impl.store.Locale.newInstance(Locale.java:593) at
org.apache.xmlbeans.impl.schema.SchemaTypeLoaderBase.newInstance(SchemaTypeLoaderBase.java:198)
at
org.apache.poi.POIXMLTypeLoader.newInstance(POIXMLTypeLoader.java:132)
at
org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRst$Factory.newInstance(Unknown
Source) at
org.apache.poi.xssf.usermodel.XSSFRichTextString.(XSSFRichTextString.java:87)
at
org.apache.poi.xssf.usermodel.XSSFCell.setCellValue(XSSFCell.java:417)
at
ups.mongo.excelutil.OutputToExcelUtils.writeData(OutputToExcelUtils.java:80)
at
ups.mongo.excelutil.OutputToExcelUtils.clusterOutToExcel(OutputToExcelUtils.java:30)
at ups.mongodb.App.main(App.java:74)
Please give me some advice for that?
Thank you with my respect.
Update solution: Using SXSSWorkbook instead of XSSWorkbook
public class OutputToExcelUtils {
private static SXSSFWorkbook workbook;
private static final String DATA_SEPERATOR = "!";
public static void clusterOutToExcel(ClusterOutput clusterObject, ClusterOutputTrade clusterOutputTrade,
ClusterOutputDistance ClusterOutputDistance, String outputPath) {
workbook = new SXSSFWorkbook();
workbook.setCompressTempFiles(true);
FileOutputStream outputStream = null;
writeData(clusterOutputTrade.getTrades(), "Data");
try {
outputStream = new FileOutputStream(outputPath);
workbook.write(outputStream);
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void writeData(List<String> data, String sheetName) {
int rowNum = 0;
SXSSFSheet sheet = workbook.createSheet(sheetName);
sheet.setRandomAccessWindowSize(100); // For 100 rows saved in memory, it will flushed after wirtten to excel file
for (int i = 0; i < data.size(); i++) {
System.out.println(sheetName + " Processing line: " + i);
int colNum = 0;
// Split into value of cell
String[] valuesOfLine = data.get(i).split(DATA_SEPERATOR);
Row row = sheet.createRow(rowNum++);
for (String valueOfCell : valuesOfLine) {
Cell cell = row.createCell(colNum++);
cell.setCellValue(valueOfCell);
}
}
}
}
Your application is spending too much time doing garbage collection. This doesn't necessarily mean that it is running out of heap space; however, it spends too much time in GC relative to performing actual work, so the Java runtime shuts it down.
Try to enable throughput collection with the following JVM option:
-XX:+UseParallelGC
While you're at it, give your application as much heap space as possible:
-Xms????m
(where ???? stands for the amount of heap space in MB, e.g. -Xms8192m)
If this doesn't help, try to set a more lenient throughput goal with this option:
-XX:GCTimeRatio=19
This specifies that your application should do 19 times more useful work than GC-related work, i.e. it allows the GC to consume up to 5% of the processor time (I believe the stricter 1% default goal may be causing the above runtime error)
No guarantee that his will work. Can you check and post back so others who experience similar problems may benefit?
EDIT
Your root problem remains the fact that you need to hold the entire spreadhseet and all its related objects in memory while you are building it. Another solution would be to serialize the data, i.e. writing the actual spreadsheet file instead of constructing it in memory and saving it at the end. However, this requires reading up on the XLXS format and creating a custom solution.
Another option would be looking for a less memory-intensive library (if one exists). Possible alternatives to POI are JExcelAPI (open source) and Aspose.Cells (commercial).
I've used JExcelAPI years ago and had a positive experience (however, it appears that it is much less actively maintained than POI, so may no longer be the best choice).
EDIT 2
Looks like POI offers a streaming model (https://poi.apache.org/spreadsheet/how-to.html#sxssf), so this may be the best overall approach.
Well try to not load all the data in memory. Even if the binary representation of 200k lines is not that big the hidrated object in memory may be too big. Just as a hint if you have a Pojo each attribute in this pojo has a pointer and each pointer depending on if it is compressed or not compressed will take 4 or 8 bytes. This mean that if your data is a Pojo with 4 attributes only for the pointers you will be spending 200 000* 4bytes(or 8 bytes).
Theoreticaly you can increase the amount of memory to the JVM, but this is not a good solution, or more precisly it is not a good solution for a Live system. For a non interactive system might be fine.
Hint: Use -Xmx -Xms jvm arguments to control the heap size.
Instead of getting the entire list from the data, iterate line wise.
If too cumbersome, write the list to a file, and reread it linewise, for instance as a Stream<String>:
Path path = Files.createTempFile(...);
Files.write(path, list, StandarCharsets.UTF_8);
Files.lines(path, StandarCharsets.UTF_8)
.forEach(line -> { ... });
On the Excel side: though xlsx uses shared strings, if XSSF was done careless,
the following would use a single String instance for repeated string values.
public class StringCache {
private static final int MAX_LENGTH = 40;
private Map<String, String> identityMap = new Map<>();
public String cached(String s) {
if (s == null) {
return null;
}
if (s.length() > MAX_LENGTH) {
return s;
}
String t = identityMap.get(s);
if (t == null) {
t = s;
identityMap.put(t, t);
}
return t;
}
}
StringCache strings = new StringCache();
for (String valueOfCell : valuesOfLine) {
Cell cell = row.createCell(colNum++);
cell.setCellValue(strings.cached(valueOfCell));
}
So I'm making a large-scale prime number generator in Java (with the help of JavaFX).
It uses the Apache POI library (I believe I'm using v3.17) to output the results to Excel spreadsheets.
The static methods for this exporting logic are held in a class called ExcelWriter. Basically, it iterates through an Arraylist arguments and populates a XSSFWorkbook with it's contents. Afterwords, a FileOutputStream is used to actually make it an excel file. Here are the relevant parts of it:
public class ExcelWriter {
//Configured JFileChooser to make alert before overwriting old files
private static JFileChooser fileManager = new JFileChooser(){
#Override
public void approveSelection(){
...
}
};
private static FileFilter filter = new FileNameExtensionFilter("Excel files","xlsx");
private static boolean hasBeenInitialized = false;
//Only method that can be called externally to access this class's functionality
public static <T extends Object> void makeSpreadsheet
(ArrayList<T> list, spreadsheetTypes type, int max, String title, JFXProgressBar progressBar)
throws IOException, InterruptedException{
progressBar.progressProperty().setValue(0);
switch (type){
case rightToLeftColumnLimit:
makeSpreadsheetRightToLeft(list, false, max, title, progressBar);
break;
...
}
}
static private <T extends Object> void makeSpreadsheetRightToLeft
(ArrayList<T> list, boolean maxRows, int max, String title, JFXProgressBar progressBar)
throws IOException, InterruptedException{
initializeChooser();
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet("Primus output");
int rowPointer = 0;
int columnPointer = 0;
double progressIncrementValue = 1/(double)list.size();
//Giving the spreadsheet an internal title also
Row row = sheet.createRow(0);
row.createCell(0).setCellValue(title);
row = sheet.createRow(++rowPointer);
//Making the sheet with a max column limit
if (!maxRows){
for (T number: list){
if (columnPointer == max){
columnPointer = 0;
row = sheet.createRow(++rowPointer);
}
Cell cell = row.createCell(columnPointer++);
progressBar.setProgress(progressBar.getProgress() + progressIncrementValue);
cell.setCellValue(number.toString());
}
}else {
//Making the sheet with a max row limit
int columnWrapIndex = (int)Math.ceil(list.size()/(float)max);
for (T number: list){
if (columnPointer == columnWrapIndex){
columnPointer = 0;
row = sheet.createRow(++rowPointer);
}
Cell cell = row.createCell(columnPointer++);
progressBar.setProgress(progressBar.getProgress() + progressIncrementValue);
cell.setCellValue(number.toString());
}
}
writeToExcel(workbook, progressBar);
}
static private void writeToExcel(XSSFWorkbook book, JFXProgressBar progressBar) throws IOException, InterruptedException{
//Exporting to Excel
int returnValue = fileManager.showSaveDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION){
File file = fileManager.getSelectedFile();
//Validation logic here
try{
FileOutputStream out = new FileOutputStream(file);
book.write(out);
out.close();
book.close();
}catch (FileNotFoundException ex){
}
}
}
}
Afterwards, my FXML document controller has a buttonListerner which calls:
longCalculationThread thread = new longCalculationThread(threadBundle);
thread.start();
The longcalculationthread creates a list of about a million prime numbers and Exports them to the ExcelWriter using this code:
private void publishResults() throws IOException, InterruptedException{
if (!longResults.isEmpty()){
if (shouldExport) {
progressText.setText("Exporting to Excel...");
ExcelWriter.makeSpreadsheet(longResults, exportType, excelExportLimit, getTitle(), progressBar);
}
}
The problem is, even though the variable holding the workbook in the XSSF workbook is a local variable to the methods it is used in, it doesn't get garbage collected afterwards.
It takes up like 1.5GB of RAM (I don't know why), and that data is only reallocated when another huge export is called (not for small exports).
My problem isn't really that the thing takes a lot of RAM, it's that even when the methods are completed the memory isn't GCed.
Here are some pictures of my NetBeans profiles:
Normal memory usage when making array of 1000000 primes:
Huge heap usage when making workbook
Memory Isn't reallocated when workbook ins't accessible anymore
Fluctuation seen when making a new workbook using the same static methods
I found the answer! I had to prompt the GC with System.gc(). I remember trying this out earlier, however I must have put it in a pace where the workbook was still accessible and hence couldn't be GCed.
I have been trying to take relations from one workbook and copying them over to another newly created workbook.
So far I have tried this:
XSSFWorkbook oldWB = new XSSFWorkbook(new File("F:\\pivottablesurvey.xlsx")); //src workbook
XSSFWorkbook newWB = new XSSFWorkbook(); //target workbook
POIXMLDocument upcastOldwb = oldWB; //Upcasting
POIXMLDocument upcastNewwb = newWB; //Upcasting
for (PackageRelationship pr : upcastOldwb.getPackagePart().getRelationships()) {
upcastNewwb.getPackagePart().getRelatedPart(pr).addRelationship(pr.getTargetURI(),pr.getTargetMode(), pr.getRelationshipType());
}
At this point, I get this error:
Exception in thread "main" java.lang.IllegalArgumentException: Relationship id=rId1 - container=org.apache.poi.openxml4j.opc.ZipPackage#5ffdc730 - relationshipType=http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet - source=/xl/workbook.xml - target=/xl/worksheets/sheet1.xml,targetMode=INTERNAL doesn't start with this part /xl/workbook.xml
First of all, I will admit that I don't even know if the approach that I have taken is correct. I am just trying to copy relations from one workbook to another workbook.
Any help will be appreciated.
Thanks
Edit 2
Is this what you are referring to when you say that that part has to exist first?
XSSFWorkbook oldWB = new XSSFWorkbook(new File("F:\\pivottablesurvey.xlsx")); //src workbook
XSSFWorkbook newWB = new XSSFWorkbook(); //target workbook
POIXMLDocument upcastOldwb = oldWB; //Upcasting
POIXMLDocument upcastNewwb = newWB; //Upcasting
//Different code from above (the actual question). Is this what you thought I missed?
for (PackageRelationship pr : upcastOldwb.getPackagePart().getRelationships()) {
URI target = pr.getTargetURI();
if(target.getFragment() != null) {
String t = target.toString();
try {
target = new URI( t.substring(0, t.indexOf('#')) );
} catch(URISyntaxException e) {
throw new InvalidFormatException("Invalid target URI: " + target);
}
}
PackagePartName relName = PackagingURIHelper.createPartName(target);
upcastNewwb.getPackagePart().getPackage().createPart(relName, upcastOldwb.getPackagePart().getContentType());
}
Edit 1:
My ultimate goal is to copy sheet(s) from one workbook to another. There are other suggestions/solutions. I even implemented one by myself without looking at other solutions.
I implemented the suggestion given here by Gagravarr. As it turned out, my implementation is 99 % the same as other solutions found here on SO and coderanch. But there is a problem with that solution. If the sheets contains tables, pictures, graphs, etc, then those solutions don't work well.
Then I thought of a clever way to copy sheets to new workbook: By process of Elimination! This solution is the best out of the rest. It keeps everything intact and no graph, chart, picture would break. But as you can tell, that is a hacky way. Not the cool way. So I want to implement something that is a proper way. Or a professional developer way.
So to do this, I looked into XSSFWorkbook.cloneSheet(...) method and how it is implemented by developers at Apache. I am trying to replicate it. So far in my attempt, every thing is going according to the plan, with one little problem. And that problem is the original question above. Let me show you my code first:
public static void main(String[] args) throws Exception {
XSSFWorkbook oldWB = new XSSFWorkbook(new File("F:\\faraz\\Documents\\pivottablesurvey.xlsx"));
XSSFWorkbook newWB = new XSSFWorkbook();
for (int i = 0; i < oldWB.getNumberOfSheets(); i++) {
XSSFSheet sheetFromOldWB = (XSSFSheet) oldWB.getSheetAt(i);
XSSFSheet sheetForNewWB = (XSSFSheet) newWB.createSheet(sheetFromOldWB.getSheetName());
/*
* Behold! Below this point, I am trying to mimic XSSFWorkbook.cloneSheet(...) method
*/
List<RelationPart> rels = sheetFromOldWB.getRelationParts();
XSSFDrawing dg = null;
for(RelationPart rp : rels) {
POIXMLDocumentPart r = rp.getDocumentPart();
if(r instanceof XSSFDrawing) {
dg = (XSSFDrawing)r;
continue;
}
addRelation(rp, sheetForNewWB); //This is a private method in XSSFWorkbook class so I copied this method over to this class
}
try {
for(PackageRelationship pr : sheetFromOldWB.getPackagePart().getRelationships()) {
if (pr.getTargetMode() == TargetMode.EXTERNAL) {
sheetForNewWB.getPackagePart().addExternalRelationship
(pr.getTargetURI().toASCIIString(), pr.getRelationshipType(), pr.getId());
}
}
} catch (InvalidFormatException e) {
throw new POIXMLException("Failed to clone sheet", e);
}
OutputStream out = new ByteArrayOutputStream();
Method writeReflect = sheetFromOldWB.getClass().
getDeclaredMethod("write", OutputStream.class); //I had to use reflection here to get it to work because write(OutputStream os) is a private method in XSSFWorkbook class
writeReflect.setAccessible(true);
Object w = writeReflect.invoke(sheetFromOldWB,out);
Method readReflect = sheetFromOldWB.getClass().
getDeclaredMethod("read", InputStream.class); //Same reason as above
readReflect.setAccessible(true);
Object r = readReflect.invoke(sheetForNewWB,new ByteArrayInputStream(((ByteArrayOutputStream) out).toByteArray()));
CTWorksheet ct = sheetForNewWB.getCTWorksheet();
if(ct.isSetLegacyDrawing()) {
System.out.println("Cloning sheets with comments is not yet supported.");
ct.unsetLegacyDrawing();
}
if (ct.isSetPageSetup()) {
System.out.println("Cloning sheets with page setup is not yet supported.");
ct.unsetPageSetup();
}
sheetForNewWB.setSelected(false);
if (dg != null) {
if(ct.isSetDrawing()) {
ct.unsetDrawing();
}
XSSFDrawing clonedDg = sheetForNewWB.createDrawingPatriarch();
clonedDg.getCTDrawing().set(dg.getCTDrawing());
clonedDg = sheetForNewWB.createDrawingPatriarch();
List<RelationPart> srcRels = sheetFromOldWB.createDrawingPatriarch().getRelationParts();
for (RelationPart rp : srcRels) {
addRelation(rp, clonedDg);
}
}
}
FileOutputStream fileOut = new FileOutputStream("F:\\faraz\\Documents\\output.xlsx");
newWB.write(fileOut);
oldWB.close();
newWB.close();
fileOut.close();
}
private static void addRelation(RelationPart rp, POIXMLDocumentPart target) {
PackageRelationship rel = rp.getRelationship();
if (rel.getTargetMode() == TargetMode.EXTERNAL) {
target.getPackagePart().addRelationship(
rel.getTargetURI(), rel.getTargetMode(), rel.getRelationshipType(), rel.getId());
} else {
XSSFRelation xssfRel = XSSFRelation.getInstance(rel.getRelationshipType());
if (xssfRel == null) {
throw new POIXMLException("Can't clone sheet - unknown relation type found: "+rel.getRelationshipType());
}
**target.addRelation(rel.getId(), xssfRel, rp.getDocumentPart());**
}
}
That line with double astericks target.addRelation(rel.getId(), xssfRel, rp.getDocumentPart()); is giving me a trouble. When I run this program as is, I get this error:
Exception in thread "main" java.lang.IllegalArgumentException: No part found for relationship id=rId1 - container=org.apache.poi.openxml4j.opc.ZipPackage#50c91c07 - relationshipType=http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable - source=/xl/worksheets/sheet1.xml - target=/xl/pivotTables/pivotTable1.xml,targetMode=INTERNAL
at org.apache.poi.openxml4j.opc.PackagePart.getRelatedPart(PackagePart.java:487)
at org.apache.poi.POIXMLDocumentPart.findExistingRelation(POIXMLDocumentPart.java:378)
at org.apache.poi.POIXMLDocumentPart.addRelation(POIXMLDocumentPart.java:343)
at oldmain.addRelation(oldmain.java:112)
at oldmain.main(oldmain.java:50)
It is looking for relations at this point! Actually, I should say I believe its looking for relation inside the workbook because I am not sure. But it cannot find them there because I am not cloning the sheets within the same workbook.
And this is my actual question here. What is it looking for here? Is what I think is correct? Is it actually looking for some relations inside the workbook? If my thinking is correct, then I need to copy all relations from source workbook to new workbook.
One thing, if I just comment out that line, then the method works okay. It would copy over everything but the graphs or pictures don't look good. I mean, it would copy over the Integers and Strings, etc but the graphs, pictures and charts would be missing. Let me show you what I meant:
Source Sheet:
Result Sheet:
Source Sheet:
Result Sheet:
You see that, it is working somewhat but not fully. And I believe that's because it's missing some kind of relations. Now, why I came to that realization is because I dug inside that line to see what is it calling and what does it want.
So, is it possible to copy all the relations from one workbook to another? Is it actually what I need for this code to work?
Thanks again and I shall really appreciate any help.
In our project, we have different versions of excelsheets which reference each other:
C:\V1\Sample.xls //no references
C:\V2\Sample.xls //references V1
C:\V3\Sample.xls //references V2
Example of a cell value:
=MID('C:\V1\[Sample.xls]Sheet1'!$AB2;21;1)
Now I want to evaluate formulas of V3 using apache POI, I found the following example here
// Create a FormulaEvaluator to use
FormulaEvaluator mainWorkbookEvaluator = workbook.getCreationHelper().createFormulaEvaluator();
// Track the workbook references
Map<String,FormulaEvaluator> workbooks = new HashMap<String, FormulaEvaluator>();
// Add this workbook
workbooks.put("report.xlsx", mainWorkbookEvaluator);
// Add two others
workbooks.put("input.xls", WorkbookFactory.create("c:\temp\input22.xls").getCreationHelper().createFormulaEvaluator());
workbooks.put("lookups.xlsx", WorkbookFactory.create("/home/poi/data/tmp-lookups.xlsx").getCreationHelper().createFormulaEvaluator());
// Attach them
mainWorkbookEvaluator.setupReferencedWorkbooks(workbooks);
// Evaluate
mainWorkbookEvaluator.evaluateAll();
Now my problem: I do not know the locations of the files, I therefore need to get all references from the mainworkbook and then automatically (and probably recursively) add them, not static like in the example above. Is there a function to get the references or does anyone know a way to achieve this?
Additionally, I am wondering if I have to add all FormulaEvaluator to V3 or do I have to add V2 to V3 and V1 to V2 for this to work?
I currently have setIgnoreMissingWorkbooks(true) implemented, but as the values will change and we do not want to open each excel file manually to update the references I want to implement this solution. Any help is appreciated
To get all external references use following method:
private static Set<String> getReferencedWorkbooks(Workbook workbook) {
Set<String> workbookNames = new HashSet<>();
final EvaluationWorkbook evalWorkbook;
if (workbook instanceof HSSFWorkbook) {
evalWorkbook = HSSFEvaluationWorkbook.create((HSSFWorkbook) workbook);
} else if (workbook instanceof XSSFWorkbook) {
evalWorkbook = XSSFEvaluationWorkbook.create((XSSFWorkbook) workbook);
} else {
throw new IllegalStateException();
}
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
Sheet sheet = workbook.getSheetAt(i);
final EvaluationSheet evalSheet = evalWorkbook.getSheet(i);
for (Row r : sheet) {
for (Cell c : r) {
if (c.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
final EvaluationCell cell = evalSheet.getCell(c.getRowIndex(), c.getColumnIndex());
final Ptg[] formulaTokens = evalWorkbook.getFormulaTokens(cell);
for (Ptg formulaToken : formulaTokens) {
final int externalSheetIndex;
if (formulaToken instanceof Ref3DPtg) {
Ref3DPtg refToken = (Ref3DPtg) formulaToken;
externalSheetIndex = refToken.getExternSheetIndex();
} else if (formulaToken instanceof Ref3DPxg) {
Ref3DPxg refToken = (Ref3DPxg) formulaToken;
externalSheetIndex = refToken.getExternalWorkbookNumber();
} else {
externalSheetIndex = -1;
}
if (externalSheetIndex >= 0) {
final ExternalSheet externalSheet = evalWorkbook.getExternalSheet(externalSheetIndex);
workbookNames.add(externalSheet.getWorkbookName());
}
}
}
}
}
}
return workbookNames;
}
If your all of your workbooks are XLSX/XLSM you can use following code:
private static Set<String> getReferencedWorkbooksXssf(XSSFWorkbook workbook) {
Set<String> workbookNames = new HashSet<>();
final List<ExternalLinksTable> externalLinksTable = workbook.getExternalLinksTable();
for (ExternalLinksTable linksTable : externalLinksTable) {
final String linkedFileName = linksTable.getLinkedFileName();
workbookNames.add(linkedFileName);
}
return workbookNames;
}