0

I'm trying to load an excel file(xlsx) into a Workbook Object using apache POI 3.10.

I'm receiving a java.lang.OutofMemoryError.

I'm using Java 8 with the -Xmx2g argument on the JVM.

All 4 cores(64bit System) and my RAM(4gb) are maxed out when I run the program.

The excel sheet has 43 columns and 166,961 Rows which equal 7,179,323 Cells.

I'm using Apache POIs WorkBookFactory.create(new File) because it uses less memory than using InputFileStream.

Does anyone have any ideas how to optimize memory usage or another way to create the Workbook?

Below is my test Reader class, don't judge, it's rough and includes debugging statements:

import java.io.File;
import java.io.IOException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

public class Reader {

    private Workbook wb;

    public Reader(File excel) {
        System.out.println("CONSTRUCTOR");
        wb = null;
        try  {
            wb = WorkbookFactory.create(excel);
        } catch (IOException e) {
            System.out.println("IO Exception");
            System.out.println(e.getMessage());
        } catch (InvalidFormatException e) {
            System.out.println("Invalid Format");
            System.out.println(e.getMessage());
        }
    }

    public boolean exists() { return (wb != null); }

    public void print() {}

    public static void main(String[] args) {
        System.out.println("START PRG");
        //File f = new File("oldfilename.xls");
        File f = new File("filename.xlsx");
        System.out.println("PATH:" + f.getAbsoluteFile());
        if (!f.exists()) {
            System.out.println("File does not exist.");
            System.exit(0);
        }
        System.out.println("FILE");
        Reader r = new Reader(f);
        System.out.println("Reader");
        r.print();
        System.out.println("PRG DONE");
    }
}
Gander7
  • 551
  • 1
  • 6
  • 17
  • How big is the excel file in mb? And if you bump the heap size up above 2gb, is there a point when it starts working? – Gagravarr Apr 18 '14 at 15:41
  • file is approx 24mb(24,333Kb) and when I had the heap size @ 4GB it still threw a java.lang.OutOfMemoryError – Gander7 Apr 28 '14 at 20:14
  • My first hunch would be that there's something very unusual about your file. Can you try following the steps in the [I think POI is using too much memory! What can I do?](http://poi.apache.org/faq.html#faq-N10109) FAQ entry, and see how it behaves with a simpler file? – Gagravarr Apr 28 '14 at 20:44

5 Answers5

2

apparently loading a 24mb file shouldn't be causing OOM...

at first glance it appears to me, though Xmx set to 2G, there's actually not that much memory free in system. in other words OS and other processes may have taken more than 2G out of 4G of physical memory! Check available physical memory first. in case available below what's expected, try closing some other running apps/processes.

if that's not the case and there's indeed enough memory left, without profiling it's really hard to identify the real cause. use a profile tool to check JVM status, related to memory first. you may simply use jconsole (as it comes with JDK). @see this on how to activate JMX

once you are connected, check readings related to memory, specifically below memory spaces:

  1. old gen
  2. young gen
  3. perm gen

monitor these spaces and see where it's struggling. I assume this is a standalone application. in case this is deployed on server (as web or services), you may consider '-XX:NewRatio' option for distributing heap spaces effectively and efficiently. @see tuning related details here.

Community
  • 1
  • 1
Anirban Basak
  • 183
  • 1
  • 4
1

Please confirm these before proceeding,

  1. Is there any infinite execution in looping(for/while)
  2. Ensure your physical storage size
  3. Maximize buffer memory

Note As per my understanding Apache POI will not consume that much amount of memory.

Surendheran
  • 177
  • 1
  • 18
  • No looping unless there is looping inside the POI New WorkbookFactory, I'm using 2Gb memory because I have 2 Gb free. It is stopping until error on New WorkbookFactory() – Gander7 Jun 04 '14 at 19:14
0

I am just a beginner, but may I ask you some questions.

  • Why not use XSSFWorkbook class to open XLSX file. I mean, I always use it to handle XLSX files, and this time I tried with a file(7 MB; that was the largest I could find in my computer), and it worked perfectly.
  • Why not use newer File API(NIO, Java 7). Again, I do not know if this will make any difference or not. But, it worked for me.

Windows 7 Ultimate | 64 bit | Intel 2nd Gen Core i3|Eclipse Juno|JDK 1.7.45|Apache POI 3.9

Path file = Paths.get("XYZABC.xlsx");
    try {
        XSSFWorkbook wb = new XSSFWorkbook(Files.newInputStream(file, StandardOpenOption.READ));
    } catch (IOException e) {
        System.out.println("Some IO Error!!!!");
    }

Do, tell if it works for you or not.

soufrk
  • 785
  • 8
  • 21
  • As detailed on the [Apache POI website](http://poi.apache.org/spreadsheet/quick-guide.html#FileInputStream), [Files use less memory than streams](http://poi.apache.org/spreadsheet/quick-guide.html#FileInputStream) so your code will use *more* memory than the OP's would! – Gagravarr May 04 '14 at 11:18
  • If you call `WorkbookFactory` with a `File` object, it passes that through to NPOIFSFileSystem or OPCPacakge already for you, no need to do anything special – Gagravarr May 04 '14 at 20:16
0

Did you tried using SXSSFWorkbook? We also used Apache POI to handle relatively big XLSX files, and we also had memory problems when using plain XSSFWorkbook. Although we didn't have to read in the files, we were just writing tens of thousands of lines of informations. Using this, our memory problems got solved. You can pass an XSSFWorkbook to its constructor and the size of data you want to keep in memory.

Sleeper9
  • 1,637
  • 17
  • 26
  • WorkbookFactory takes advantage of both using the Factory Design Pattern. – Gander7 Jun 04 '14 at 19:15
  • SXSSFWorkbook cannot be used to read files, only to create new files. See the table on [POI website](https://poi.apache.org/spreadsheet/#SXSSF+%28Since+POI+3.8+beta3%29) – Valentin Feb 27 '15 at 15:25
0

Java 1.8 based on HSSF and XSSF Limitations my poi version is 3.17 POI Examples

lauches my code

public class Controller {
    EX stressTest;
    public void fineFile() {
        String stresstest = "C:\\Stresstest.xlsx";
        HashMap<String, String[]> stressTestMap = new HashMap<>();
        stressTestMap.put("aaaa", new String[]{"myField", "The field"});
        stressTestMap.put("bbbb", new String[]{"other", "Other value"});
        try {
            InputStream stressTestIS = new FileInputStream(stresstest);
            stressTest = new EX(stresstest, stressTestIS, stressTestMap);
        } catch (IOException exp) {
        }
    }
    public void printErr() {
        if (stressTest.thereAreErrors()) {
            try {
                FileWriter myWriter = new FileWriter(
                        "C:\\logErrorsStressTest" +
                                (new SimpleDateFormat("ddMMyyyyHHmmss")).format(new Date()) +
                                ".txt"
                );
                myWriter.write(stressTest.getBodyFileErrors());
                myWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
        }
    }
    public void createBD() {
        List<OneObjectWhatever> entitiesList =
                (
                        !stressTest.thereAreErrors()
                                ? ((List<OneObjectWhatever>) stressTest.toListCustomerObject(OneObjectWhatever.class))
                                : new ArrayList<>()
                );
        entitiesList.forEach(entity -> {
            Field[] fields = entity.getClass().getDeclaredFields();
            String valueString = "";
            for (Field attr : fields) {
                try {
                    attr.setAccessible(true);
                    valueString += " StressTest:" + attr.getName() + ": -" + attr.get(fields) + "- ";
                    attr.setAccessible(true);
                } catch (Exception reflectionError) {
                    System.out.println(reflectionError);
                }
            }
        });
    }
}

MY CODE

public class EX {
    private HashMap<Integer, HashMap<Integer, String> > rows;
    private List<String> errors;

    private int maxColOfHeader, minColOfHeader;
    private HashMap<Integer, String> header;
    private HashMap<String,String[]> relationHeaderClassPropertyDescription;

    private void initVariables(String name, InputStream file) {
        this.rows = new HashMap();
        this.header = new HashMap<>();
        this.errors = new ArrayList<String>(){{add("["+name+"] empty cells in position -> ");}};

        try{
            InputStream is = FileMagic.prepareToCheckMagic(file);
            FileMagic fm = FileMagic.valueOf(is);
            is.close();

            switch (fm) {
                case OLE2:
                    XLS2CSVmra xls2csv = new XLS2CSVmra(name, 50, rows);
                    xls2csv.process();
                    System.out.println("OLE2");
                    break;
                case OOXML:
                    File flatFile = new File(name);
                    OPCPackage p = OPCPackage.open(flatFile, PackageAccess.READ);
                    XLSX2CSV xlsx2csv = new XLSX2CSV(p, System.out, 50, this.rows);
                    xlsx2csv.process();
                    p.close();
                    System.out.println("OOXML");
                    break;
                default:
                    System.out.println("Your InputStream was neither an OLE2 stream, nor an OOXML stream");
                    break;
            }

        } catch (IOException | EncryptedDocumentException | SAXException | OpenXML4JException exp){
            System.out.println(exp);
            exp.printStackTrace();
        }
        
        int rowHeader = rows.keySet().stream().findFirst().get();

        this.header.putAll(rows.get(rowHeader));

        this.rows.remove(rowHeader);

        this.minColOfHeader = this.header.keySet().stream().findFirst().get();

        this.maxColOfHeader = this.header.entrySet().stream()
                .mapToInt(e -> e.getKey()).max()
                .orElseThrow(NoSuchElementException::new);

    }

    public EX(String name, InputStream file, HashMap<String,String[]> relationHeaderClassPropertyDescription_) {
        this.relationHeaderClassPropertyDescription = relationHeaderClassPropertyDescription_;
        initVariables(name, file);
        validate();
    }

    private void validate(){
        rows.forEach((inx,row) -> {
            for(int i = minColOfHeader; i <= maxColOfHeader; i++) {
                //System.out.println("r:"+inx+" c:"+i+" cr:"+(!row.containsKey(i))+" vr:"+((!row.containsKey(i)) || row.get(i).trim().isEmpty())+" ch:"+header.containsKey(i)+" vh:"+(header.containsKey(i) && (!header.get(i).trim().isEmpty()))+" val:"+(row.containsKey(i)&&!row.get(i).trim().isEmpty()?row.get(i):"empty"));
                if((!row.containsKey(i)) || row.get(i).trim().isEmpty()) {
                    if(header.containsKey(i) && (!header.get(i).trim().isEmpty())) {
                        String description = getRelationHeaders(i,1);
                        errors.add(" ["+header.get(i)+"]{"+description+"} = fila: "+(inx+1)+" - columna: "+ CellReference.convertNumToColString(i));
                        // System.out.println(" fila: "+inx+" - columna: " + i + " - valor: "+ (row.get(i).isEmpty()?"empty":row.get(i)));
                    }
                }
            }
        });
        header.forEach((i,v)->{System.out.println("stressTestMap.put(\""+v+"\", new String[]{\"{"+i+"}\",\"Mi descripcion XD\"});");});
    }

    public String getBodyFileErrors()
    {
        return String.join(System.lineSeparator(), errors);
    }

    public boolean thereAreErrors() {
        return errors.stream().count() > 1;
    }


    public<T extends Class> List<? extends Object> toListCustomerObject(T type) {
        List<Object> list = new ArrayList<>();
        rows.forEach((inx, row) -> {
            try {
                Object obj = type.newInstance();
                for(int i = minColOfHeader; i <= maxColOfHeader; i++) {
                    if (row.containsKey(i) && !row.get(i).trim().isEmpty()) {
                        if (header.containsKey(i) && !header.get(i).trim().isEmpty()) {
                            if(relationHeaderClassPropertyDescription.containsKey(header.get(i))) {
                                String nameProperty = getRelationHeaders(i,0);
                                Field field = type.getDeclaredField(nameProperty);
                                    try{
                                        field.setAccessible(true);
                                        field.set(obj, (isConvertibleTo(field.getType(),row.get(i)) ? toObject(field.getType(),row.get(i)) : defaultValue(field.getType())) );
                                        field.setAccessible(false);
                                    }catch (Exception fex) {
                                        //System.out.println("113"+fex);
                                        continue;
                                    }
                            }
                        }
                    }
                }
                list.add(obj);
            } catch (Exception ex) {
                //System.out.println("123:"+ex);
            }
        });
        return list;
    }
    private Object toObject( Class clazz, String value ) {
        if( Boolean.class == clazz || Boolean.TYPE == clazz) return Boolean.parseBoolean( value );
        if( Byte.class == clazz || Byte.TYPE == clazz) return Byte.parseByte( value );
        if( Short.class == clazz || Short.TYPE == clazz) return Short.parseShort( value );
        if( Integer.class == clazz || Integer.TYPE == clazz) return Integer.parseInt( value );
        if( Long.class == clazz || Long.TYPE == clazz) return Long.parseLong( value );
        if( Float.class == clazz || Float.TYPE == clazz) return Float.parseFloat( value );
        if( Double.class == clazz || Double.TYPE == clazz) return Double.parseDouble( value );
        return value;
    }

    private boolean isConvertibleTo( Class clazz, String value ) {
        String ptn = "";
        if( Boolean.class == clazz || Boolean.TYPE == clazz) ptn = ".*";
        if( Byte.class == clazz || Byte.TYPE == clazz) ptn = "^\\d+$";
        if( Short.class == clazz || Short.TYPE == clazz) ptn = "^\\d+$";
        if( Integer.class == clazz || Integer.TYPE == clazz) ptn = "^\\d+$";
        if( Long.class == clazz || Long.TYPE == clazz) ptn = "^\\d+$";
        if( Float.class == clazz || Float.TYPE == clazz) ptn = "^\\d+(\\.\\d+)?$";
        if( Double.class == clazz || Double.TYPE == clazz) ptn = "^\\d+(\\.\\d+)?$";
        Pattern pattern = Pattern.compile(ptn, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(value);
        return matcher.find();
    }

    private Object defaultValue( Class clazz) {
        if( Boolean.class == clazz || Boolean.TYPE == clazz) return Boolean.parseBoolean( "false" );
        if( Byte.class == clazz || Byte.TYPE == clazz) return Byte.parseByte( "0" );
        if( Short.class == clazz || Short.TYPE == clazz) return Short.parseShort( "0" );
        if( Integer.class == clazz || Integer.TYPE == clazz) return Integer.parseInt( "0" );
        if( Long.class == clazz || Long.TYPE == clazz) return Long.parseLong( "0" );
        if( Float.class == clazz || Float.TYPE == clazz) return Float.parseFloat( "0.0" );
        if( Double.class == clazz || Double.TYPE == clazz) return Double.parseDouble( "0.0" );
        return "";
    }
    private String getRelationHeaders(Integer columnIndexHeader, Integer TypeOrDescription /*0 - Type, 1 - Description*/) {
        try {
            return relationHeaderClassPropertyDescription.get(header.get(columnIndexHeader))[TypeOrDescription];
        } catch (Exception e) {

        }
        return header.get(columnIndexHeader);
    }
}

these are the modifications I made to the examples:

XLSX2CSV

public class XLSX2CSV {
    /**
     * Uses the XSSF Event SAX helpers to do most of the work
     *  of parsing the Sheet XML, and outputs the contents
     *  as a (basic) CSV.
     */
    private class SheetToCSV implements SheetContentsHandler {
        private boolean firstCellOfRow = false;
        private int currentRow = -1;
        private int currentCol = -1;
        HashMap<Integer, String> valuesCell;

        private void outputMissingRows(int number) {
            for (int i=0; i<number; i++) {
                for (int j=0; j<minColumns; j++) {
                    output.append(',');
                }
                output.append('\n');
            }
        }

        @Override
        public void startRow(int rowNum) {
            // If there were gaps, output the missing rows
            outputMissingRows(rowNum-currentRow-1);
            // Prepare for this row
            firstCellOfRow = true;
            currentRow = rowNum;
            currentCol = -1;
            valuesCell = new HashMap<>();
        }

        @Override
        public void endRow(int rowNum) {
            // Ensure the minimum number of columns
            for (int i = currentCol; i < minColumns; i++) {
                output.append(',');
            }
            output.append('\n');
            if (!valuesCell.isEmpty())
                _rows.put(rowNum, valuesCell);
        }

        @Override
        public void cell(String cellReference, String formattedValue,
                         XSSFComment comment) {
            if (firstCellOfRow) {
                firstCellOfRow = false;
            } else {
                output.append(',');
            }

            // gracefully handle missing CellRef here in a similar way as XSSFCell does
            if (cellReference == null) {
                cellReference = new CellAddress(currentRow, currentCol).formatAsString();
            }

            // Did we miss any cells?
            int thisCol = (new CellReference(cellReference)).getCol();
            int missedCols = thisCol - currentCol - 1;
            for (int i = 0; i < missedCols; i++) {
                output.append(',');
            }
            currentCol = thisCol;

            if (!formattedValue.isEmpty())
                valuesCell.put(thisCol, formattedValue);

            // Number or string?
            output.append(formattedValue);
            /*try {
                //noinspection ResultOfMethodCallIgnored
                Double.parseDouble(formattedValue);
                output.append(formattedValue);
            } catch (NumberFormatException e) {
                output.append('"');
                output.append(formattedValue);
                output.append('"');
            }*/
        }

        @Override
        public void headerFooter(String text, boolean isHeader, String tagName) {
            // Skip, no headers or footers in CSV
        }
    }


    ///////////////////////////////////////

    private final OPCPackage xlsxPackage;

    /**
     * Number of columns to read starting with leftmost
     */
    private final int minColumns;

    /**
     * Destination for data
     */
    private final PrintStream output;
    public HashMap<Integer, HashMap<Integer, String>> _rows;
    /**
     * Creates a new XLSX -> CSV converter
     *
     * @param pkg        The XLSX package to process
     * @param output     The PrintStream to output the CSV to
     * @param minColumns The minimum number of columns to output, or -1 for no minimum
     */
    public XLSX2CSV(OPCPackage pkg, PrintStream output, int minColumns, HashMap<Integer, HashMap<Integer, String> > __rows) {
        this.xlsxPackage = pkg;
        this.output = output;
        this.minColumns = minColumns;
        this._rows = __rows;
    }

    /**
     * Parses and shows the content of one sheet
     * using the specified styles and shared-strings tables.
     *
     * @param styles The table of styles that may be referenced by cells in the sheet
     * @param strings The table of strings that may be referenced by cells in the sheet
     * @param sheetInputStream The stream to read the sheet-data from.

     * @exception java.io.IOException An IO exception from the parser,
     *            possibly from a byte stream or character stream
     *            supplied by the application.
     * @throws SAXException if parsing the XML data fails.
     */
    public void processSheet(
            StylesTable styles,
            ReadOnlySharedStringsTable strings,
            SheetContentsHandler sheetHandler,
            InputStream sheetInputStream) throws IOException, SAXException {
        DataFormatter formatter = new DataFormatter();
        InputSource sheetSource = new InputSource(sheetInputStream);
        try {
            XMLReader sheetParser = SAXHelper.newXMLReader();
            ContentHandler handler = new XSSFSheetXMLHandler(
                    styles, null, strings, sheetHandler, formatter, false);
            sheetParser.setContentHandler(handler);
            sheetParser.parse(sheetSource);
        } catch(ParserConfigurationException e) {
            throw new RuntimeException("SAX parser appears to be broken - " + e.getMessage());
        }
    }

    /**
     * Initiates the processing of the XLS workbook file to CSV.
     *
     * @throws IOException If reading the data from the package fails.
     * @throws SAXException if parsing the XML data fails.
     */
    public void process() throws IOException, OpenXML4JException, SAXException {
        ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(this.xlsxPackage);
        XSSFReader xssfReader = new XSSFReader(this.xlsxPackage);
        StylesTable styles = xssfReader.getStylesTable();
        XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader.getSheetsData();
        int index = 0;
        while (iter.hasNext()) {
            InputStream stream = iter.next();
            String sheetName = iter.getSheetName();
            this.output.println();
            this.output.println(sheetName + " [index=" + index + "]:");
            processSheet(styles, strings, new SheetToCSV(), stream);
            stream.close();
            ++index;
            break;
        }
    }
}

XLS2CSVmra

public class XLS2CSVmra implements HSSFListener {
    private int minColumns;
    private POIFSFileSystem fs;
    private PrintStream output;
    public HashMap<Integer, HashMap<Integer, String>> _rows;
    private HashMap<Integer, String> valuesCell;

    private int lastRowNumber;
    private int lastColumnNumber;

    /** Should we output the formula, or the value it has? */
    private boolean outputFormulaValues = false;

    /** For parsing Formulas */
    private SheetRecordCollectingListener workbookBuildingListener;
    private HSSFWorkbook stubWorkbook;

    // Records we pick up as we process
    private SSTRecord sstRecord;
    private FormatTrackingHSSFListener formatListener;

    /** So we known which sheet we're on */
    private int sheetIndex = -1;
    private BoundSheetRecord[] orderedBSRs;
    private List<BoundSheetRecord> boundSheetRecords = new ArrayList<BoundSheetRecord>();

    // For handling formulas with string results
    private int nextRow;
    private int nextColumn;
    private boolean outputNextStringRecord;

    /**
     * Creates a new XLS -> CSV converter
     * @param fs The POIFSFileSystem to process
     * @param output The PrintStream to output the CSV to
     * @param minColumns The minimum number of columns to output, or -1 for no minimum
     */
    public XLS2CSVmra(POIFSFileSystem fs, PrintStream output, int minColumns, HashMap<Integer, HashMap<Integer, String>> __rows) {
        this.fs = fs;
        this.output = output;
        this.minColumns = minColumns;
        this._rows = __rows;
        this.valuesCell = new HashMap<>();
    }

    /**
     * Creates a new XLS -> CSV converter
     * @param filename The file to process
     * @param minColumns The minimum number of columns to output, or -1 for no minimum
     * @throws IOException
     * @throws FileNotFoundException
     */
    public XLS2CSVmra(String filename, int minColumns, HashMap<Integer, HashMap<Integer, String>> __rows) throws IOException, FileNotFoundException {
        this(
                new POIFSFileSystem(new FileInputStream(filename)),
                System.out, minColumns,
                __rows
        );
    }

    /**
     * Initiates the processing of the XLS file to CSV
     */
    public void process() throws IOException {
        MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(this);
        formatListener = new FormatTrackingHSSFListener(listener);

        HSSFEventFactory factory = new HSSFEventFactory();
        HSSFRequest request = new HSSFRequest();

        if(outputFormulaValues) {
            request.addListenerForAllRecords(formatListener);
        } else {
            workbookBuildingListener = new SheetRecordCollectingListener(formatListener);
            request.addListenerForAllRecords(workbookBuildingListener);
        }

        factory.processWorkbookEvents(request, fs);
    }

    /**
     * Main HSSFListener method, processes events, and outputs the
     *  CSV as the file is processed.
     */
    @Override
    public void processRecord(Record record) {
        if(sheetIndex>0)
            return;

        int thisRow = -1;
        int thisColumn = -1;
        String thisStr = null;

        switch (record.getSid())
        {
            case BoundSheetRecord.sid:
                if(sheetIndex==-1)
                boundSheetRecords.add((BoundSheetRecord)record);
                break;
            case BOFRecord.sid:
                BOFRecord br = (BOFRecord)record;
                if(br.getType() == BOFRecord.TYPE_WORKSHEET && sheetIndex==-1) {
                    // Create sub workbook if required
                    if(workbookBuildingListener != null && stubWorkbook == null) {
                        stubWorkbook = workbookBuildingListener.getStubHSSFWorkbook();
                    }

                    // Output the worksheet name
                    // Works by ordering the BSRs by the location of
                    //  their BOFRecords, and then knowing that we
                    //  process BOFRecords in byte offset order
                    sheetIndex++;
                    if(orderedBSRs == null) {
                        orderedBSRs = BoundSheetRecord.orderByBofPosition(boundSheetRecords);
                    }
                    output.println();
                    output.println(
                            orderedBSRs[sheetIndex].getSheetname() +
                                    " [" + (sheetIndex+1) + "]:"
                    );
                }
                break;

            case SSTRecord.sid:
                sstRecord = (SSTRecord) record;
                break;

            case BlankRecord.sid:
                BlankRecord brec = (BlankRecord) record;

                thisRow = brec.getRow();
                thisColumn = brec.getColumn();
                thisStr = "";
                break;
            case BoolErrRecord.sid:
                BoolErrRecord berec = (BoolErrRecord) record;

                thisRow = berec.getRow();
                thisColumn = berec.getColumn();
                thisStr = "";
                break;

            case FormulaRecord.sid:
                FormulaRecord frec = (FormulaRecord) record;

                thisRow = frec.getRow();
                thisColumn = frec.getColumn();

                if(outputFormulaValues) {
                    if(Double.isNaN( frec.getValue() )) {
                        // Formula result is a string
                        // This is stored in the next record
                        outputNextStringRecord = true;
                        nextRow = frec.getRow();
                        nextColumn = frec.getColumn();
                    } else {
                        thisStr = formatListener.formatNumberDateCell(frec);
                    }
                } else {
                    thisStr = '"' +
                            HSSFFormulaParser.toFormulaString(stubWorkbook, frec.getParsedExpression()) + '"';
                }
                break;
            case StringRecord.sid:
                if(outputNextStringRecord) {
                    // String for formula
                    StringRecord srec = (StringRecord)record;
                    thisStr = srec.getString();
                    thisRow = nextRow;
                    thisColumn = nextColumn;
                    outputNextStringRecord = false;
                }
                break;

            case LabelRecord.sid:
                LabelRecord lrec = (LabelRecord) record;

                thisRow = lrec.getRow();
                thisColumn = lrec.getColumn();
                thisStr = '"' + lrec.getValue() + '"';
                break;
            case LabelSSTRecord.sid:
                LabelSSTRecord lsrec = (LabelSSTRecord) record;

                thisRow = lsrec.getRow();
                thisColumn = lsrec.getColumn();
                if(sstRecord == null) {
                    thisStr = '"' + "(No SST Record, can't identify string)" + '"';
                } else {
                    thisStr = '"' + sstRecord.getString(lsrec.getSSTIndex()).toString() + '"';
                }
                break;
            case NoteRecord.sid:
                NoteRecord nrec = (NoteRecord) record;

                thisRow = nrec.getRow();
                thisColumn = nrec.getColumn();
                // TODO: Find object to match nrec.getShapeId()
                thisStr = '"' + "(TODO)" + '"';
                break;
            case NumberRecord.sid:
                NumberRecord numrec = (NumberRecord) record;

                thisRow = numrec.getRow();
                thisColumn = numrec.getColumn();

                // Format
                thisStr = formatListener.formatNumberDateCell(numrec);
                break;
            case RKRecord.sid:
                RKRecord rkrec = (RKRecord) record;

                thisRow = rkrec.getRow();
                thisColumn = rkrec.getColumn();
                thisStr = '"' + "(TODO)" + '"';
                break;
            default:
                break;
        }

        // Handle new row
        if(thisRow != -1 && thisRow != lastRowNumber) {
            lastColumnNumber = -1;
        }

        // Handle missing column
        if(record instanceof MissingCellDummyRecord) {
            MissingCellDummyRecord mc = (MissingCellDummyRecord)record;
            thisRow = mc.getRow();
            thisColumn = mc.getColumn();
            thisStr = "";
        }

        // If we got something to print out, do so
        if(thisStr != null) {
            if (thisColumn > 0) {
                output.print(',');
            }
            if (!thisStr.isEmpty())
                valuesCell.put(thisColumn, thisStr);
            output.print(thisStr);
        }

        // Update column and row count
        if(thisRow > -1)
            lastRowNumber = thisRow;
        if(thisColumn > -1)
            lastColumnNumber = thisColumn;

        // Handle end of row
        if(record instanceof LastCellOfRowDummyRecord) {
            // Print out any missing commas if needed
            if(minColumns > 0) {
                // Columns are 0 based
                if(lastColumnNumber == -1) { lastColumnNumber = 0; }
                for(int i=lastColumnNumber; i<(minColumns); i++) {
                    output.print(',');
                }
            }

            // We're onto a new row
            lastColumnNumber = -1;

            // End the row
            output.println();

            if(!valuesCell.isEmpty()) {
                HashMap<Integer, String> newRow = new HashMap<>();
                valuesCell.forEach((inx,vStr) -> {
                    newRow.put(inx, vStr);
                });
                _rows.put(lastRowNumber, newRow);
                valuesCell = new HashMap<>();
            }
        }
    }
}
tu matador
  • 11
  • 3