/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2015 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ package org.pentaho.di.trans.steps.excelinput.staxpoi; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.io.InputStream; import java.util.Collections; import java.util.Date; import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.StylesTable; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTRst; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTXf; import org.pentaho.di.core.spreadsheet.KCell; import org.pentaho.di.core.spreadsheet.KCellType; import org.pentaho.di.core.spreadsheet.KSheet; public class StaxPoiSheetTest { private static final String BP_SHEET = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + "<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" " + "xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">" + "%s" + "</worksheet>"; private static final String SHEET_DATE_NO_V = String.format( BP_SHEET, " <dimension ref=\"A1:A3\"/>" + " <sheetData>" + " <row r=\"1\" spans=\"1:1\">" + " <c r=\"A1\" s=\"1\" t=\"s\"><v>0</v></c>" + " </row>" + " <row r=\"2\" spans=\"1:1\">" + " <c r=\"A2\" s=\"2\"><v>42248</v></c>" + " </row>" + " <row r=\"3\" spans=\"1:1\">" + " <c r=\"A3\" s=\"2\"/>" + " </row>" + " </sheetData>" ); private static final String SHEET_1 = String.format( BP_SHEET, " <dimension ref=\"B2:F5\"/>" + " <sheetData>" + " <row r=\"2\" spans=\"2:6\"><c r=\"B2\" t=\"s\"><v>0</v></c><c r=\"C2\" t=\"s\"><v>1</v></c>" + " <c r=\"D2\" t=\"s\"><v>2</v></c><c r=\"E2\" t=\"s\"><v>3</v></c><c r=\"F2\" t=\"s\"><v>4</v></c></row>" + " <row r=\"3\" spans=\"2:6\"><c r=\"B3\" t=\"s\"><v>5</v></c><c r=\"C3\" s=\"1\"><v>40428</v></c>" + " <c r=\"D3\"><v>75</v></c><c r=\"E3\" t=\"b\"><v>1</v></c><c r=\"F3\"><f>D3</f><v>75</v></c></row>" + " <row r=\"4\" spans=\"2:6\"><c r=\"B4\" t=\"s\"><v>6</v></c><c r=\"C4\" s=\"1\"><v>40429</v></c>" + " <c r=\"D4\"><v>42</v></c><c r=\"E4\" t=\"b\"><v>0</v></c><c r=\"F4\"><f>F3+D4</f><v>117</v></c></row>" + " <row r=\"5\" spans=\"2:6\"><c r=\"B5\" t=\"s\"><v>7</v></c><c r=\"C5\" s=\"1\"><v>40430</v></c>" + " <c r=\"D5\"><v>93</v></c><c r=\"E5\" t=\"b\"><v>1</v></c><c r=\"F5\"><f>F4+D5</f><v>210</v></c></row>" + " </sheetData>" ); private static final String SHEET_EMPTY = String.format( BP_SHEET, "<dimension ref=\"A1\"/><sheetData/>" ); @Test public void testNullDateCell() throws Exception { // cell had null value instead of being null final String sheetId = "1"; final String sheetName = "Sheet 1"; XSSFReader reader = mockXSSFReader( sheetId, SHEET_DATE_NO_V, mockSharedStringsTable( "Some Date" ), mockStylesTable( Collections.singletonMap( 2, 165 ), Collections.singletonMap( 165, "M/D/YYYY" ) ) ); StaxPoiSheet spSheet = new StaxPoiSheet( reader, sheetName, sheetId ); KCell cell = spSheet.getRow( 1 )[0]; assertNotNull( cell ); assertEquals( KCellType.DATE, cell.getType() ); cell = spSheet.getRow( 2 )[0]; assertNull( "cell must be null", cell ); } @Test public void testEmptySheet() throws Exception { XSSFReader reader = mockXSSFReader( "sheet1", SHEET_EMPTY, mock( SharedStringsTable.class ), mock( StylesTable.class ) ); // check no exceptions StaxPoiSheet sheet = new StaxPoiSheet( reader, "empty", "sheet1" ); for ( int j = 0; j < sheet.getRows(); j++ ) { sheet.getRow( j ); } } @Test public void testReadSameRow() throws Exception { KSheet sheet1 = getSampleSheet(); KCell[] row = sheet1.getRow( 3 ); assertEquals( "Two", row[1].getValue() ); row = sheet1.getRow( 3 ); assertEquals( "Two", row[1].getValue() ); } @Test public void testReadRowRA() throws Exception { KSheet sheet1 = getSampleSheet(); KCell[] row = sheet1.getRow( 4 ); assertEquals( "Three", row[1].getValue() ); row = sheet1.getRow( 2 ); assertEquals( "One", row[1].getValue() ); } @Test public void testReadEmptyRow() throws Exception { KSheet sheet1 = getSampleSheet(); KCell[] row = sheet1.getRow( 0 ); assertEquals( "empty row expected", 0, row.length ); } @Test public void testReadCells() throws Exception { KSheet sheet = getSampleSheet(); KCell cell = sheet.getCell( 1, 2 ); assertEquals( "One", cell.getValue() ); assertEquals( KCellType.LABEL, cell.getType() ); cell = sheet.getCell( 2, 2 ); assertEquals( KCellType.DATE, cell.getType() ); assertEquals( new Date( 1283817600000L ), cell.getValue() ); cell = sheet.getCell( 1, 3 ); assertEquals( "Two", cell.getValue() ); assertEquals( KCellType.LABEL, cell.getType() ); } @Test public void testReadData() throws Exception { KSheet sheet1 = getSampleSheet(); assertEquals( 5, sheet1.getRows() ); KCell[] row = sheet1.getRow( 2 ); assertEquals( KCellType.LABEL, row[1].getType() ); assertEquals( "One", row[1].getValue() ); assertEquals( KCellType.DATE, row[2].getType() ); assertEquals( new Date( 1283817600000L ), row[2].getValue() ); assertEquals( KCellType.NUMBER, row[3].getType() ); assertEquals( Double.valueOf( "75" ), row[3].getValue() ); assertEquals( KCellType.BOOLEAN, row[4].getType() ); assertEquals( Boolean.TRUE, row[4].getValue() ); assertEquals( KCellType.NUMBER_FORMULA, row[5].getType() ); assertEquals( Double.valueOf( "75" ), row[5].getValue() ); row = sheet1.getRow( 3 ); assertEquals( KCellType.LABEL, row[1].getType() ); assertEquals( "Two", row[1].getValue() ); assertEquals( KCellType.DATE, row[2].getType() ); assertEquals( new Date( 1283904000000L ), row[2].getValue() ); assertEquals( KCellType.NUMBER, row[3].getType() ); assertEquals( Double.valueOf( "42" ), row[3].getValue() ); assertEquals( KCellType.BOOLEAN, row[4].getType() ); assertEquals( Boolean.FALSE, row[4].getValue() ); assertEquals( KCellType.NUMBER_FORMULA, row[5].getType() ); assertEquals( Double.valueOf( "117" ), row[5].getValue() ); row = sheet1.getRow( 4 ); assertEquals( KCellType.LABEL, row[1].getType() ); assertEquals( "Three", row[1].getValue() ); assertEquals( KCellType.DATE, row[2].getType() ); assertEquals( new Date( 1283990400000L ), row[2].getValue() ); assertEquals( KCellType.NUMBER, row[3].getType() ); assertEquals( Double.valueOf( "93" ), row[3].getValue() ); assertEquals( KCellType.BOOLEAN, row[4].getType() ); assertEquals( Boolean.TRUE, row[4].getValue() ); assertEquals( KCellType.NUMBER_FORMULA, row[5].getType() ); assertEquals( Double.valueOf( "210" ), row[5].getValue() ); try { row = sheet1.getRow( 5 ); fail( "No out of bounds exception thrown when expected" ); } catch ( ArrayIndexOutOfBoundsException e ) { // OK! } } private StaxPoiSheet getSampleSheet() throws Exception { String sheetId = "sheet1"; XSSFReader reader = mockXSSFReader( sheetId, SHEET_1, mockSharedStringsTable( "Col1Label", "Col2Date", "Col3Number", "Col4Boolean", "Col5NumFormula", "One", "Two", "Three" ), mockStylesTable( Collections.singletonMap( 1, 14 ), Collections.<Integer, String>emptyMap() ) ); return new StaxPoiSheet( reader, "Sheet 1", sheetId ); } private XSSFReader mockXSSFReader( final String sheetId, final String sheetContent, final SharedStringsTable sst, final StylesTable styles ) throws Exception { XSSFReader reader = mock( XSSFReader.class ); when( reader.getSharedStringsTable() ).thenReturn( sst ); when( reader.getStylesTable() ).thenReturn( styles ); when( reader.getSheet( sheetId ) ).thenAnswer( new Answer<InputStream>() { public InputStream answer( InvocationOnMock invocation ) throws Throwable { return IOUtils.toInputStream( sheetContent, "UTF-8" ); } } ); return reader; } private StylesTable mockStylesTable( final Map<Integer, Integer> styleToNumFmtId, final Map<Integer, String> numFmts ) { StylesTable styles = mock( StylesTable.class ); when( styles.getCellXfAt( any( Integer.class ) ) ).then( new Answer<CTXf>() { public CTXf answer( InvocationOnMock invocation ) throws Throwable { int style = (int) invocation.getArguments()[0]; Integer numFmtId = styleToNumFmtId.get( style ); if ( numFmtId != null ) { CTXf ctxf = CTXf.Factory.newInstance(); ctxf.setNumFmtId( numFmtId ); return ctxf; } else { return null; } } } ); when( styles.getNumberFormatAt( any( Integer.class ) ) ).then( new Answer<String>() { public String answer( InvocationOnMock invocation ) throws Throwable { return numFmts.get( (Integer) invocation.getArguments()[0] ); } } ); return styles; } private SharedStringsTable mockSharedStringsTable( String... strings ) { SharedStringsTable sst = new SharedStringsTable(); for ( String str : strings ) { CTRst st = CTRst.Factory.newInstance(); st.setT( str ); sst.addEntry( st ); } return sst; } }