001package org.hl7.fhir.r5.testfactory.dataprovider;
002
003import org.apache.poi.ss.usermodel.*;
004import org.apache.poi.ss.util.CellReference;
005import org.hl7.fhir.exceptions.FHIRException;
006import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
007import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
008import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
009import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
010
011import java.io.FileInputStream;
012import java.io.IOException;
013import java.util.ArrayList;
014import java.util.List;
015import java.util.Locale;
016import java.util.HashMap;
017import java.util.Map;
018
019
020@MarkedToMoveToAdjunctPackage
021public class ExcelDataProvider extends TableDataProvider {
022
023  private Workbook workbook;
024  private Sheet sheet;
025  private List<String> columnHeaders;
026  private int currentRowIndex = -1;
027  private Map<String, Integer> columnIndexMap = new HashMap<>();
028  private Row currentRow;
029  private DataFormatter df;
030  private int startRow = 0;
031  private int startCol = 0;
032  private int endRow = -1;
033  private int endCol = -1;
034
035  /**
036   * Constructs an ExcelTableDataProvider.
037   *
038   * @param filename  The path to the Excel file.
039   * @param sheetName The name of the sheet to read.
040   * @param sheetName The range of the sheet to read.
041   * @throws IOException If an I/O error occurs.
042   * @throws InvalidFormatException If the file format is invalid.
043   */
044  public ExcelDataProvider(String filename, String sheetName, String range, Locale locale) throws IOException, InvalidFormatException {
045    FileInputStream fis = new FileInputStream(ManagedFileAccess.file(filename));
046    this.workbook = WorkbookFactory.create(fis);
047    if (sheetName != null) {
048      this.sheet = workbook.getSheet(sheetName);
049    }
050
051    df = new DataFormatter(locale);
052    if (sheet == null) {
053      List<String> names = new ArrayList<String>();
054      for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
055        names.add(workbook.getSheetName(i));
056      }
057      if (sheetName == null && names.size() > 0) {
058        this.sheet = workbook.getSheet(names.get(0));        
059      } else {
060        throw new IllegalArgumentException("Sheet '" + sheetName + "' does not exist in the file. Sheet Names = "+CommaSeparatedStringBuilder.join(",", names));
061      }
062    }
063    if (range != null ) {
064      String[] parts = range.split(":");
065      CellReference startCell = new CellReference(parts[0]);
066      startRow = startCell.getRow();
067      startCol = startCell.getCol();
068      if (parts.length==2) {
069        CellReference endCell = new CellReference(parts[1]);
070        endRow = endCell.getRow();
071        endCol = endCell.getCol();
072      }
073    }
074    loadColumnHeaders();
075  }
076
077
078  public ExcelDataProvider(String filename) throws InvalidFormatException, IOException {
079    FileInputStream fis = new FileInputStream(ManagedFileAccess.file(filename));
080    this.workbook = WorkbookFactory.create(fis);
081    this.sheet = workbook.getSheetAt(0);
082    loadColumnHeaders();
083  }
084
085
086  /**
087   * Loads the column headers from the first row of the sheet.
088   */
089  private void loadColumnHeaders() {
090    columnHeaders = new ArrayList<>();
091    columnHeaders.add("counter");
092    Row headerRow = sheet.getRow(startRow);
093    if (headerRow != null) {
094      for (Cell cell : headerRow) {
095        if (cell.getColumnIndex()>= startCol && (endCol==-1 || cell.getColumnIndex()<= endCol )) {
096          String headerName = cell.getStringCellValue().trim();
097          columnHeaders.add(headerName);
098          columnIndexMap.put(headerName, cell.getColumnIndex());
099        }
100      }
101    }
102  }
103
104  @Override
105  public List<String> columns() {
106    return columnHeaders;
107  }
108
109  @Override
110  public boolean nextRow() {
111    currentRowIndex++;
112    currentRow = sheet.getRow(startRow + currentRowIndex + 1); // Skip the header row
113    if (currentRow != null && endRow!=-1 && endRow == startRow + currentRowIndex) {
114      currentRow = null;
115    }
116    return currentRow != null;
117  }
118
119  @Override
120  public List<String> cells() {
121    List<String> cellValues = new ArrayList<>();
122    cellValues.add(""+(currentRowIndex+1));
123    if (currentRow != null) {
124      for (Cell cell : currentRow) {
125        int i = cell.getColumnIndex();
126        if (i>= startCol && (endCol==-1 || i<= endCol )) {
127          while (cellValues.size() <= i-startCol) {
128            cellValues.add("");
129          }
130          cellValues.add(getCellValue(cell).trim());
131        }
132      }
133    }
134    return cellValues;
135  }
136
137  @Override
138  public String cell(String name) {
139    if ("counter".equals(name)) {
140      return ""+currentRowIndex;      
141    } else {
142      Integer columnIndex = columnIndexMap.get(name);
143      if (columnIndex == null || currentRow == null) {
144        return null;
145      }
146      Cell cell = currentRow.getCell(columnIndex);
147      return cell == null ? null : getCellValue(cell).trim();
148    }
149  }
150
151  /**
152   * Utility method to get a cell value as a string.
153   *
154   * @param cell The cell.
155   * @return The cell value as a string.
156   */
157  private String getCellValue(Cell cell) {
158    switch (cell.getCellType()) {
159    case STRING:
160      return cell.getStringCellValue();
161    case NUMERIC:
162      return df.formatCellValue(cell);
163    case BOOLEAN:
164      return Boolean.toString(cell.getBooleanCellValue());
165    case FORMULA:
166      return cell.getCellFormula();
167    case BLANK:
168      return "";
169    default:
170      return "";
171    }
172  }
173
174  /**
175   * Closes the workbook and releases resources.
176   *
177   * @throws IOException If an I/O error occurs.
178   */
179  public void close() throws IOException {
180    if (workbook != null) {
181      workbook.close();
182    }
183  }
184
185  @Override
186  public void reset() throws FHIRException {
187    currentRowIndex = -1;
188  }
189
190}