blob: 2b6472480a87d9ba3d3700be4977abc76b117423 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Chalmers | University of Gothenburg, rt-labs and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Chalmers | University of Gothenburg and rt-labs - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.capra.ui.office.model;
import java.awt.Desktop;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.poi.hssf.OldExcelFormatException;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.eclipse.capra.ui.office.exceptions.CapraOfficeObjectNotFound;
import org.eclipse.capra.ui.office.preferences.OfficePreferences;
import org.eclipse.capra.ui.office.utils.CapraOfficeUtils;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSelection;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheetView;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheetViews;
import com.google.common.io.Files;
/**
* This class extends the CapraOfficeObject and provides an object to describe a
* single MS Excel row.
*
* @author Dusan Kalanj
*
*/
public class CapraExcelRow extends CapraOfficeObject {
/**
* RegEx of characters (tabs, newlines, carriage returns and invisible
* control characters) to be replaced with white-spaces in the Office View.
*/
private static final String LINE_BREAKS_AND_CONTROL_REQ = "[\r\n\t\\p{C}]+";
private static final DataFormatter FORMATTER = new DataFormatter();
/**
* Delimiter between excel cells as displayed in the Office View.
*/
private static final String CELL_DELIMITER = " | ";
/**
* A constant that is used if the row index isn't found when opening object
* details.
*/
private static final int NO_ROW_INDEX = -1;
/**
* A constant that is used if the last cell in the row isn't found.
*/
private static final String NO_LAST_CELL_REFERENCE = "-1";
/**
* The ID of the column that is used to extract the identifier of the row
* (if value is OfficePreferences.EXCEL_COLUMN_VALUE_DEFAULT, line numbers
* are used as identifiers). The property is set according to the value in
* the Capra preference page in Eclipse preferences.
*/
private String idColumn;
/**
* A constructor that generates a new instance of CapraExcelRow where the
* parent properties are extracted from the provided Excel row and File
* object that contains the row.
*
* @param officeFile
* a File object representing an Excel file.
* @param row
* an Excel row, extracted from the provided Excel file.
* @param idColumn
* the excel column that is used to extract the ID of the row
*/
public CapraExcelRow(File officeFile, Row row, String idColumn) {
super();
this.idColumn = idColumn;
String rowId = getRowIdFromExcelRow(row);
StringBuilder rowBuilder = new StringBuilder();
rowBuilder.append("ID " + rowId + ": ");
// Gather the text from the whole Excel row into one StringBuilder
boolean firstCellSet = false;
for (int j = 1; j < row.getLastCellNum(); j++) {
Cell cell = row.getCell(j);
String cellValue = FORMATTER.formatCellValue(cell);
if (!cellValue.isEmpty()) {
if (!firstCellSet) {
rowBuilder.append(cellValue);
firstCellSet = true;
} else {
rowBuilder.append(CELL_DELIMITER + cellValue);
}
}
}
// Set data and uri fields if any data was extracted from the row. If no
// data was found, data and uri will be empty strings (as defined in the
// CapraOfficeObject - parent object).
if (firstCellSet) {
Pattern p = Pattern.compile(LINE_BREAKS_AND_CONTROL_REQ);
Matcher m = p.matcher(rowBuilder);
String rowData = (m.replaceAll(" ")).trim();
String rowUriEnd = row.getSheet().getSheetName() + CapraOfficeObject.URI_DELIMITER + rowId;
String rowUri = createUri(officeFile.getAbsolutePath(), rowUriEnd);
this.setData(rowData);
this.setUri(rowUri);
}
}
@Override
public void showOfficeObjectInNativeEnvironment() throws CapraOfficeObjectNotFound {
// Get Excel file from the CapraExcelRow object.
File officeFile;
try {
officeFile = getFile();
} catch (NoSuchFileException e) {
e.printStackTrace();
return;
}
// Extract relevant info from the object.
String fileType = Files.getFileExtension(officeFile.getAbsolutePath());
String rowId = getRowIdFromObjectUri();
String sheetName = getSheetName();
// Get the object's sheet
Sheet sheet = null;
try {
Workbook workBook = CapraOfficeUtils.getExcelWorkbook(officeFile);
sheet = CapraOfficeUtils.getSheet(workBook, sheetName);
} catch (OldExcelFormatException | IOException e) {
e.printStackTrace();
return;
}
// Find the Excel row that the CapraExcelRow object points to.
// lastCellReference is used to store the column ID (such as B or C2) of
// the last cell that contains any data in the row.
String lastCellReference = NO_LAST_CELL_REFERENCE;
int rowIndex = NO_ROW_INDEX;
for (int i = 0; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row != null) {
String currRowId = getRowIdFromExcelRow(row);
if (currRowId.equals(rowId)) {
rowIndex = i;
lastCellReference = CellReference.convertNumToColString(row.getLastCellNum()) + (rowIndex + 1);
break;
}
}
}
if (rowIndex == NO_ROW_INDEX || lastCellReference == NO_LAST_CELL_REFERENCE)
throw new CapraOfficeObjectNotFound(getRowIdFromObjectUri());
// firstDisplayedRowIndex is used to set the first visible row in the
// view that opens to the user - for example if the row in question is
// at index 50, the solution will open the Excel file at row 48.
int firstDisplayedRowIndex = (rowIndex - 2 > 0) ? rowIndex - 2 : 1;
// The next block of code highlights the selected row in the file by
// setting some XML values in the excel file.
if (fileType.equals(CapraOfficeObject.XLSX)) {
XSSFSheet xssfSheet = XSSFSheet.class.cast(sheet);
int sheetIndex = xssfSheet.getWorkbook().getSheetIndex(xssfSheet);
xssfSheet.getWorkbook().setActiveSheet(sheetIndex);
CTSheetViews ctSheetViews = xssfSheet.getCTWorksheet().getSheetViews();
CTSheetView ctSheetView = ctSheetViews.getSheetViewArray(ctSheetViews.sizeOfSheetViewArray() - 1);
ctSheetView.setTopLeftCell("A" + firstDisplayedRowIndex);
CTSelection ctSelection = ctSheetView.addNewSelection();
ctSelection.setActiveCell("A" + (rowIndex + 1));
ctSelection.setSqref(Arrays.asList("A" + (rowIndex + 1) + ":" + lastCellReference));
} else {
HSSFSheet hssfSheet = HSSFSheet.class.cast(sheet);
hssfSheet.setActive(true);
hssfSheet.showInPane((short) (rowIndex), (short) 0);
HSSFRow row = hssfSheet.getRow(rowIndex);
HSSFCell cell = row.getCell(0);
cell.setAsActiveCell(); // TODO doesn't work - bug in library?
}
// Overwrites the existing Excel file with the new XML values.
try (FileOutputStream out = new FileOutputStream(getFile())) {
sheet.getWorkbook().write(out);
} catch (IOException e) {
e.printStackTrace();
}
// Opens the Excel file with the Excel application.
// TODO If Excel is already open, this doesn't trigger. Is there a way
// to refresh the (Excel) application?
try {
Desktop.getDesktop().open(getFile());
} catch (IOException e) {
e.printStackTrace();
return;
}
}
/**
* Extracts the name of the sheet (from the URI of the object) which the
* object is associated with. The URI of the object should always be in the
* format fileId + DELIMITER + sheetName + DELIMITER + rowId.
*
* @return name of the sheet
*/
public String getSheetName() {
String itemId = getId();
int lastIndexOfDelimiter = itemId.indexOf(CapraOfficeObject.URI_DELIMITER);
return itemId.substring(0, lastIndexOfDelimiter);
}
/**
* Extracts the ID of the row from the URI of the object. The URI of the
* object should always be in the format fileId + DELIMITER + sheetName +
* DELIMITER + rowId.
*
* @return ID of the row
*/
private String getRowIdFromObjectUri() {
String itemId = getId();
int lastIndexOfDelimiter = itemId.indexOf(CapraOfficeObject.URI_DELIMITER);
return itemId.substring(lastIndexOfDelimiter + CapraOfficeObject.URI_DELIMITER.length());
}
/**
* Extracts the ID of the Excel row (not CapraExcelRow!) based on the
* idColumn value. If the value of idColumn is EXCEL_COLUMN_VALUE_DEFAULT,
* then rowID is the same as row number, otherwise a specific column
* (defined by idColumn property - such as A1) is used to extract the data
* that will serve as the ID of the row.
*
* @param row
* the row to identify
* @return the ID of the row
*/
protected String getRowIdFromExcelRow(Row row) {
String rowId = "";
if (idColumn.equals(OfficePreferences.EXCEL_COLUMN_VALUE_DEFAULT))
rowId = Integer.toString(row.getRowNum() + 1);
else
rowId = FORMATTER.formatCellValue(row.getCell(CellReference.convertColStringToIndex(idColumn)));
return rowId;
}
}