如何使用 Apache-POI 计算 Excel 中的公式并避免出现 "save changes" 消息框?

How to calculate Formulas in Excel with Apache-POI and avoid the "save changes" messagebox?

我遇到了以下问题:我编写了一个 Java 程序,将值写入 xlsx 文件。此 xlsx 文件使用公式计算新值。现在我想从 xlsx 文件中获取这个计算值。问题是,我没有将计算值输入我的 Java 程序,因为更改没有保存。 所以我尝试编辑 xlsx 文件中的 xl/workbook.xml 以摆脱保存更改问题。这行得通,但现在我尝试读取的公式单元格 return 默认值而不是计算值。所以 atm 我有两个选择:我使用

workbook.setForceFormulaRecalculation(true)

计算我在不手动保存文件的情况下无法读取的值。或者我编辑 xl/workbook.xml 以避免手动保存文件,但是公式不计算值..在这两种情况下我的程序只能读取默认值而不是我计算的值想.. 这是我编辑 xml:

的代码
 public void editXML(String path) throws FileNotFoundException, IOException{

    ZipFile zipFile = new ZipFile(path);
    final ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("D:\Excels\SO_Berechnung_nosave.xlsx"));
    for(Enumeration e = zipFile.entries(); e.hasMoreElements();){
        ZipEntry entryIn = (ZipEntry) e.nextElement();
//    if(!(entryIn.getName().equalsIgnoreCase("xl/workbook.xml"))){
            System.out.println(entryIn.getName());
            zos.putNextEntry(entryIn);
            InputStream is = zipFile.getInputStream(entryIn);
            byte[] buffer = new byte[4096];
            int len;
            while((len = (is.read(buffer)))>0){
                zos.write(buffer, 0, len);
            }

//        } 
        zos.flush();
        zos.closeEntry();
    } 

    File excel = new File("D:\Excels\SO_Berechnung_nosave.xlsx");
    FileInputStream fis = new FileInputStream(excel);
    XSSFWorkbook book = new XSSFWorkbook(fis);
    FileOutputStream fos = new FileOutputStream("D:\Excels\SO_Berechnung_nosave.xlsx");
    book.setForceFormulaRecalculation(true);
    book.write(fos);
    fis.close();
    fos.flush();
    fos.close();

    for(Enumeration e = zipFile.entries(); e.hasMoreElements();){
        System.out.println("????????????????????????");
        ZipEntry entryIn = (ZipEntry) e.nextElement();
        if(entryIn.getName().equalsIgnoreCase("xl/workbook.xml")){
            System.out.println("RIGHT ENTRY FOUND AND WORKBOOK:XML WILL BE CHANGED NOW");
            zos.putNextEntry(new ZipEntry("xl\workbook.xml"));
            System.out.println("RIGHT ENTRY FOUND AND WORKBOOK:XML WILL BE CHANGED NOW");
            InputStream is = zipFile.getInputStream(entryIn);
            byte[] buffer = new byte[2048];
            int len;
            while(is.read(buffer) >= 0){
                String s = new String(buffer);
                //Add standallone yes
                String sFirstLine = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
                String rFirstLine = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
                if(s.contains(sFirstLine)){
                    s = s.replace(sFirstLine, rFirstLine);
                    System.out.println("firstLine old: " + sFirstLine);
                    System.out.println("firstLine new: " + rFirstLine);
                }

                //replace wrong path
                String sPath = "\Empty";
                String rPath = "";
                if(s.contains(sPath)){
                    s = s.replaceAll(Pattern.quote(sPath), Matcher.quoteReplacement(rPath));
                    System.out.println("path old: " + sPath);
                    System.out.println("path new: " + rPath);
                }

                //replace FileVersion
                String searchFileVersion = "/main\"><fileVersion appName=\"xl\" lastEdited=\"6\" lowestEdited=\"6\" rupBuild=\"14420\"/>"; //I know its 2times the same
                String replaceFileVersion =  "/main\"><fileVersion appName=\"xl\" lastEdited=\"6\" lowestEdited=\"6\" rupBuild=\"14420\"/>";//the rup Build should be right
                if(s.contains(searchFileVersion)){
                    s = s.replaceAll(searchFileVersion, replaceFileVersion);
                    System.out.println("fileVersion old: " + searchFileVersion);
                    System.out.println("fileVersion new: " + replaceFileVersion);
                }

                //replace calcId
                String searchCalcId = "<calcPr calcId=\"0\"/>";
                String replaceCalcId = "<calcPr calcId=\"152511\"/>"; //2147483647   152511
                if(s.contains(searchCalcId)){
                    s = s.replaceAll(searchCalcId, replaceCalcId);
                    System.out.println("calcId old: " + searchCalcId);
                    System.out.println("calcId new: " + replaceCalcId);
                }

                //replace Alternate
                String searchAlternateContent = "<mc:AlternateContent>";
                String replaceAlternateContent = "<mc:AlternateContent xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\">";
                if(s.contains(searchAlternateContent)){
                    s = s.replaceAll(searchAlternateContent, replaceAlternateContent);
                    System.out.println("AlternateContent old: " + searchAlternateContent);
                    System.out.println("AlternateContent new: " + replaceAlternateContent);
                }
                //idk if this has impact...
                String searchXmlns = "mc:Ignorable=\"x15\" "
                        + "xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" "
                        + "xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" "
                        + "xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" "
                        + "xmlns:x15=\"http://schemas.microsoft.com/office/spreadsheetml/2010/11/main\">";
                String replaceXmlns = "xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" "
                        + "xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" "
                        + "xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" "
                        + "mc:Ignorable=\"x15\" "
                        + "xmlns:x15=\"http://schemas.microsoft.com/office/spreadsheetml/2010/11/main\">";
                if(s.contains(searchXmlns)){
                    s = s.replaceAll(searchXmlns, replaceXmlns);
                    System.out.println("AlternateContent old: " + searchXmlns);
                    System.out.println("AlternateContent new: " + replaceXmlns);
                }

                //replace last line
                String sWb = "</workbook";
                String rWb = "</workbook>";
                if(s.contains(sWb)){
                    s = s.replaceAll(sWb, rWb);
                    System.out.println("Workbook old: " + sWb);
                    System.out.println("Workbook new: " + rWb);
                }

                System.out.println("");
                System.out.println(s);
                System.out.println("");

                len = s.trim().length();
                buffer = s.getBytes();
                zos.write(buffer, 0, (len < buffer.length) ? len : buffer.length);
            }
        }
    }

    zos.flush();
    zos.closeEntry();
    zos.close();


}

我已经尝试将除 xl/workbook.xml 之外的所有 xml 文件复制到新创建的 xlsx,然后导入编辑后的 ​​xl/workbook.xml 但后来公式不起作用.. 并且为那个意大利面条代码感到抱歉,但我尝试了很多东西并且不想删除可能导致我找到工作解决方案的东西。我所做的所有替换的解释:我在保存之前和保存之后比较了 xl/workbook.xml 并消除了所有差异。如果我现在比较 xml 就没有区别了。

更新:

根据接受的答案,我不再使用上面的代码。我补充说 book.getCreationHelper().createFormulaEvaluator().evaluateAll(); 在以下上下文中添加到我的代码中:

 FileOutputStream fos = new FileOutputStream(path);
 book.setForceFormulaRecalculation(true);
 //XSSFFormulaEvaluator.evaluateAllFormulaCells((XSSFWorkbook) book);
 book.getCreationHelper().createFormulaEvaluator().evaluateAll();
 book.write(fos);
 fis.close();
 fos.flush();
 fos.close(); 

现在我得到以下异常:

Exception in thread "AWT-EventQueue-0" java.lang.RuntimeException: Invalid ValueEval type passed for conversion: (class org.apache.poi.ss.formula.eval.MissingArgEval)
at org.apache.poi.ss.formula.functions.MultiOperandNumericFunction.collectValue(MultiOperandNumericFunction.java:219)
at org.apache.poi.ss.formula.functions.MultiOperandNumericFunction.collectValues(MultiOperandNumericFunction.java:179)
at org.apache.poi.ss.formula.functions.MultiOperandNumericFunction.getNumberArray(MultiOperandNumericFunction.java:128)
at org.apache.poi.ss.formula.functions.MultiOperandNumericFunction.evaluate(MultiOperandNumericFunction.java:90)
at org.apache.poi.ss.formula.OperationEvaluatorFactory.evaluate(OperationEvaluatorFactory.java:132)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluateFormula(WorkbookEvaluator.java:540)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluateAny(WorkbookEvaluator.java:303)
at org.apache.poi.ss.formula.WorkbookEvaluator.evaluate(WorkbookEvaluator.java:245)
at org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator.evaluateFormulaCellValue(XSSFFormulaEvaluator.java:268)
at org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator.evaluateFormulaCell(XSSFFormulaEvaluator.java:155)
at org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.evaluateAllFormulaCells(HSSFFormulaEvaluator.java:335)
at org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.evaluateAllFormulaCells(HSSFFormulaEvaluator.java:326)
at org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator.evaluateAll(XSSFFormulaEvaluator.java:256)
at XLSXEditor.searchWriter(XLSXEditor.java:218)
at Main.fillTable(Main.java:962)
at Main.btShowActionPerformed(Main.java:715)
at Main.access0(Main.java:25)
at Main.actionPerformed(Main.java:402)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2022)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2348)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:252)
at java.awt.Component.processMouseEvent(Component.java:6533)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
at java.awt.Component.processEvent(Component.java:6298)
at java.awt.Container.processEvent(Container.java:2236)
at java.awt.Component.dispatchEventImpl(Component.java:4889)
at java.awt.Container.dispatchEventImpl(Container.java:2294)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4888)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4525)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4466)
at java.awt.Container.dispatchEventImpl(Container.java:2280)
at java.awt.Window.dispatchEventImpl(Window.java:2746)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758)
at java.awt.EventQueue.access0(EventQueue.java:97)
at java.awt.EventQueue.run(EventQueue.java:709)
at java.awt.EventQueue.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:90)
at java.awt.EventQueue.run(EventQueue.java:731)
at java.awt.EventQueue.run(EventQueue.java:729)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:80)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:728)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

我检查了我在我的公式中使用的所有功能 here 并且它们都提供了.. 是否存在由本地化或其他原因引起的问题?

"I wrote a Java programm that writes values into a xlsx file. This xlsx file calculates new values using Formulas. Now I want to get this calculated Values out of the xlsx file. The Problem is, that I don't get the calculated values into my Java programm, because changes aren't saved. "

到目前为止,您的假设都是正确的。

"So I tryed to edit the xl/workbook.xml in the xlsx file to get rid of that save changes question. "

但是现在你走错了路

workbook.setForceFormulaRecalculation(true) 将公式的重新计算委托给 Excel 的 GUI。如果 Excel 的 GUI 下次打开文件,则重新计算完成。重新计算后, 发生了变化。所以关于保存更改的问题是合法的。只有保存更改后,即重新计算的公式结果,这些结果才会存储在文件中。然后,在从该文件新建 Workbook 后,结果只能使用 apache poi 读取。

但是委托重新计算只是一个选项,例如recalculating formulas. The other option is using a FormulaEvaluator and it's evaluateAll方法。

...
workbook.getCreationHelper().createFormulaEvaluator().evaluateAll();
...

之后,所有公式都将使用 apache poi 计算,新结果将可重读。

当然,这仅适用于 apache poiFormulaEvaluator 支持的公式。有些是不支持的。那么你不能简单地做 evaluateAll,而必须只评估包含支持的公式的单元格。

在这种情况下,关于 Formula Evaluation 的整个章节可能很有趣。

更新:

如前所述,目前并非所有公式都受支持。 apache poi 不像 Excel 那样宽容。

根据问题 Update 部分的错误,公式中使用了 MissingArgEval 又名 "a missed argument",通常不应该有这样错过的论点。

例如,Excel 容忍在 0 的公式中简单地不提供任何参数作为参数。对于前。 =INDEX(A:A,MATCH("Text",Z:Z,)) 而不是 =INDEX(A:A,MATCH("Text",Z:Z,0))。但是 apache poi FormulaEvaluator 不会容忍的。

所以您现在需要调查导致错误的公式。因此,evaluateAll 对单元格进行循环以按照 Formula Evaluation - "Re-calculating all formulas in a Workbook":

中所述进行评估
FormulaEvaluator evaluator = book.getCreationHelper().createFormulaEvaluator();
for (Sheet sheet : book) {
    for (Row r : sheet) {
        for (Cell c : r) {
            if (c.getCellType() == Cell.CELL_TYPE_FORMULA) {
System.out.println(c.getAddress() + ":" + c.getCellFormula());
                evaluator.evaluateFormulaCell(c);
            }
        }
    }
}

调试 System.out.println 应该会告诉您是哪个单元格导致了问题。