diff --git a/app/databrowser/pom.xml b/app/databrowser/pom.xml
index 6c30bc13bd..ce81134175 100644
--- a/app/databrowser/pom.xml
+++ b/app/databrowser/pom.xml
@@ -75,5 +75,37 @@
commons-math3
${apache.commons.math.version}
+
+
+ org.apache.poi
+ poi
+
+
+ com.zaxxer
+ SparseBitSet
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ jcl-over-slf4j
+
+
+ commons-codec
+ commons-codec
+
+
+ org.apache.commons
+ commons-collections4
+
+
+ org.apache.commons
+ commons-math3
+
+
+ 5.0.0
+
diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/Messages.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/Messages.java
index c25c88cc3b..92cee51e28 100644
--- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/Messages.java
+++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/Messages.java
@@ -118,6 +118,9 @@ public class Messages
ExportStartExport,
ExportTabular,
ExportTabularTT,
+ ExportTypeExcel,
+ ExportTypeExcelFilenamePrompt,
+ ExportTypeExcelTT,
ExportTypeMatlab,
ExportTypeMatlabTT,
ExportTypeSpreadsheet,
diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/ExcelExportJob.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/ExcelExportJob.java
new file mode 100644
index 0000000000..ec0c5e7e65
--- /dev/null
+++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/ExcelExportJob.java
@@ -0,0 +1,384 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Oak Ridge National Laboratory.
+ * 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
+ ******************************************************************************/
+package org.csstudio.trends.databrowser3.export;
+
+import java.io.PrintStream;
+import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import org.csstudio.trends.databrowser3.Messages;
+import org.csstudio.trends.databrowser3.model.ArchiveDataSource;
+import org.csstudio.trends.databrowser3.model.Model;
+import org.csstudio.trends.databrowser3.model.ModelItem;
+import org.csstudio.trends.databrowser3.model.PVItem;
+import org.epics.vtype.VEnum;
+import org.epics.vtype.VNumber;
+import org.epics.vtype.VStatistics;
+import org.epics.vtype.VString;
+import org.epics.vtype.VType;
+import org.phoebus.archive.reader.SpreadsheetIterator;
+import org.phoebus.archive.reader.ValueIterator;
+import org.phoebus.archive.vtype.VTypeHelper;
+import org.phoebus.framework.jobs.JobMonitor;
+import org.phoebus.util.time.SecondsParser;
+import org.phoebus.util.time.TimestampFormats;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+
+/** Export Job for exporting data from Model as Excel file
+ * @author Kay Kasemir
+ */
+@SuppressWarnings("nls")
+public class ExcelExportJob extends ExportJob
+{
+ private Workbook wb = null;
+ private CellStyle comment_style, header_style, timestamp_style;
+ private Sheet sheet;
+ private Row row;
+ private ZoneId zone = ZoneId.systemDefault();
+ private final boolean tabular, min_max, sevr_stat;
+
+ /** @param model Model
+ * @param start Start time
+ * @param end End time
+ * @param source Data source
+ * @param tabular Create one combined table? Otherwise one table per channel
+ * @param min_max Show min/max info (error) for statistical data?
+ * @param sevr_stat Include alarm severity and status?
+ * @param optimize_parameter Bin count
+ * @param filename Export file name
+ * @param error_handler Error handler
+ * @param unixTimeStamp Use UNIX time stamp epoch?
+ */
+ public ExcelExportJob(final Model model,
+ final Instant start, final Instant end, final Source source,
+ final boolean tabular, final boolean min_max, final boolean sevr_stat,
+ final double optimize_parameter,
+ final String filename,
+ final Consumer error_handler,
+ final boolean unixTimeStamp)
+ {
+ super("", model, start, end, source, optimize_parameter, filename, error_handler, unixTimeStamp);
+ this.tabular = tabular;
+ this.min_max = min_max;
+ this.sevr_stat = sevr_stat;
+ }
+
+ private void addComment(final Row row, final String label, final String text)
+ {
+ Cell cell = row.createCell(0, CellType.STRING);
+ cell.setCellValue(label);
+ cell.setCellStyle(comment_style);
+ if (text != null)
+ {
+ cell = row.createCell(1, CellType.STRING);
+ cell.setCellValue(text);
+ cell.setCellStyle(comment_style);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void printExportInfo(final PrintStream out) throws Exception
+ {
+ // Called first and may throw Exception, so create workbook etc in here
+ wb = new HSSFWorkbook();
+
+ comment_style = wb.createCellStyle();
+ Font font = wb.createFont();
+ font.setBold(true);
+ font.setColor(IndexedColors.DARK_BLUE.getIndex());
+ comment_style.setFont(font);
+
+ header_style = wb.createCellStyle();
+ font = wb.createFont();
+ font.setItalic(true);
+ header_style.setFont(font);
+ header_style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+ header_style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+
+ timestamp_style = wb.createCellStyle();
+ timestamp_style.setDataFormat(
+ wb.getCreationHelper()
+ .createDataFormat()
+ .getFormat("yyyy-mm-dd hh:mm:ss.000"));
+
+ // Create sheet with summary of exported data
+ sheet = wb.createSheet("Archive Data");
+
+ addComment(row = sheet.createRow(0), "Created by CS-Studio Data Browser", null);
+ addComment(row = sheet.createRow(row.getRowNum() + 2), "Start time", TimestampFormats.MILLI_FORMAT.format(start));
+ addComment(row = sheet.createRow(row.getRowNum() + 1), "End time", TimestampFormats.MILLI_FORMAT.format(end));
+ addComment(row = sheet.createRow(row.getRowNum() + 1), "Source", source.toString());
+
+ if (source == Source.OPTIMIZED_ARCHIVE)
+ addComment(row = sheet.createRow(row.getRowNum() + 1), "Desired Value Count", Double.toString(optimize_parameter));
+ else if (source == Source.LINEAR_INTERPOLATION)
+ addComment(row = sheet.createRow(row.getRowNum() + 1), "Interpolation Interval", SecondsParser.formatSeconds(optimize_parameter));
+ }
+
+ /** @param row Row where to create time stamp cell
+ * @param time Timestamp to place in cell
+ * @return The cell in column 0 of row
+ */
+ private Cell createTimeCell(final Row row, final Instant time)
+ {
+ Cell cell = row.createCell(0, CellType.NUMERIC);
+ if (unixTimeStamp)
+ cell.setCellValue(time.toEpochMilli());
+ else
+ {
+ cell.setCellValue(LocalDateTime.ofInstant(time, zone));
+ cell.setCellStyle(timestamp_style);
+ }
+ return cell;
+ }
+
+ /** @param row Row where to create value cell
+ * @param column Column index
+ * @param value Value to show
+ * @return Cell that was created for the value
+ */
+ private Cell createValueCell(final Row row, final int column, final VType value)
+ {
+ final Cell cell;
+ if (value instanceof VNumber v)
+ {
+ cell = row.createCell(column, CellType.NUMERIC);
+ cell.setCellValue(v.getValue().doubleValue());
+ }
+ else if (value instanceof VStatistics v)
+ {
+ cell = row.createCell(column, CellType.NUMERIC);
+ cell.setCellValue(v.getAverage());
+ }
+ else if (value instanceof VEnum v)
+ {
+ cell = row.createCell(column, CellType.NUMERIC);
+ cell.setCellValue(v.getIndex());
+ }
+ else if (value instanceof VString v)
+ {
+ cell = row.createCell(column, CellType.STRING);
+ cell.setCellValue(v.getValue());
+ }
+ else if (value == null)
+ cell = row.createCell(column, CellType.BLANK);
+ else
+ {
+ cell = row.createCell(column, CellType.STRING);
+ cell.setCellValue(Objects.toString(value));
+ }
+ return cell;
+ }
+
+ /** Create basic value cell as well as optional min/max and sevr/stat cells
+ * @param row Row where to create cells
+ * @param column Index of first cell column
+ * @param value Value to show in cell(s)
+ * @return Last cell created
+ */
+ private Cell createValueCells(final Row row, final int column, final VType value)
+ {
+ Cell cell = createValueCell(row, column, value);
+ if (min_max)
+ {
+ if (value instanceof VStatistics stats)
+ { // Turn min..max into negative & positive error
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.NUMERIC);
+ cell.setCellValue(stats.getAverage() - stats.getMin());
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.NUMERIC);
+ cell.setCellValue(stats.getMax() - stats.getAverage());
+ }
+ else
+ {
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.BLANK);
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.BLANK);
+ }
+ }
+ if (sevr_stat)
+ {
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.STRING);
+ cell.setCellValue(Objects.toString(org.phoebus.core.vtypes.VTypeHelper.getSeverity(value)));
+
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.STRING);
+ cell.setCellValue(VTypeHelper.getMessage(value));
+ }
+ return cell;
+ }
+
+
+ @Override
+ protected void performExport(final JobMonitor monitor, final PrintStream out) throws Exception
+ {
+ // Item header
+ for (ModelItem item : model.getItems())
+ {
+ addComment(row = sheet.createRow(row.getRowNum() + 2), "Channel", item.getResolvedName());
+ if (! item.getName().equals(item.getDisplayName()))
+ addComment(row = sheet.createRow(row.getRowNum() + 1), "Name", item.getResolvedDisplayName());
+
+ if (item instanceof PVItem)
+ {
+ final PVItem pv = (PVItem) item;
+ addComment(row = sheet.createRow(row.getRowNum() + 1), "Archives:", null);
+
+ int i=1;
+ for (ArchiveDataSource archive : pv.getArchiveDataSources())
+ {
+ addComment(row = sheet.createRow(row.getRowNum() + 1),
+ i + ") " + archive.getName(),
+ "URL " + archive.getUrl());
+ ++i;
+ }
+ }
+ }
+
+ if (tabular)
+ exportTable(monitor);
+ else
+ exportList(monitor);
+
+ wb.write(out);
+ }
+
+ /** Export data in combined table */
+ private void exportTable(final JobMonitor monitor) throws Exception
+ {
+ // Spreadsheet data header
+ row = sheet.createRow(row.getRowNum() + 2);
+ Cell cell = row.createCell(0, CellType.STRING);
+ cell.setCellStyle(header_style);
+ cell.setCellValue(Messages.TimeColumn);
+ for (ModelItem item : model.getItems())
+ {
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.STRING);
+ cell.setCellStyle(header_style);
+ cell.setCellValue(item.getResolvedName());
+
+ if (min_max)
+ {
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.STRING);
+ cell.setCellStyle(header_style);
+ cell.setCellValue(Messages.NegErrColumn);
+
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.STRING);
+ cell.setCellStyle(header_style);
+ cell.setCellValue(Messages.PosErrColumn);
+ }
+ if (sevr_stat)
+ {
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.STRING);
+ cell.setCellStyle(header_style);
+ cell.setCellValue(Messages.SeverityColumn);
+
+ cell = row.createCell(cell.getColumnIndex()+1, CellType.STRING);
+ cell.setCellStyle(header_style);
+ cell.setCellValue(Messages.StatusColumn);
+ }
+ }
+
+ // Create spreadsheet interpolation
+ final List iters = new ArrayList<>();
+ for (ModelItem item : model.getItems())
+ {
+ monitor.beginTask(MessageFormat.format("Fetching data for {0}", item.getName()));
+ iters.add(createValueIterator(item));
+ }
+ final SpreadsheetIterator iter = new SpreadsheetIterator(iters.toArray(new ValueIterator[iters.size()]));
+ // Dump the spreadsheet lines
+ long line_count = 0;
+ while (iter.hasNext() && !monitor.isCanceled())
+ {
+ final Instant time = iter.getTime();
+ final VType line[] = iter.next();
+
+ cell = createTimeCell(row = sheet.createRow(row.getRowNum() + 1), time);
+ for (int i=0; iperformExport */
- protected void printExportInfo(final PrintStream out)
+ /** Print file header, gets invoked before performExport
+ * @param out PrintStream for output
+ * @throws Exception on error
+ */
+ protected void printExportInfo(final PrintStream out) throws Exception
{
out.println(comment + "Created by CS-Studio Data Browser");
out.println(comment);
diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/MatlabScriptExportJob.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/MatlabScriptExportJob.java
index e925add970..357e1735ab 100644
--- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/MatlabScriptExportJob.java
+++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/MatlabScriptExportJob.java
@@ -48,7 +48,7 @@ public MatlabScriptExportJob(final Model model, final Instant start,
/** {@inheritDoc} */
@Override
- protected void printExportInfo(final PrintStream out)
+ protected void printExportInfo(final PrintStream out) throws Exception
{
super.printExportInfo(out);
out.println(comment);
diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/PlainExportJob.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/PlainExportJob.java
index 74992cb76a..356dcfcc64 100644
--- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/PlainExportJob.java
+++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/PlainExportJob.java
@@ -45,15 +45,13 @@ public PlainExportJob(final Model model,
final String filename,
final Consumer error_handler,
final boolean unixTimeStamp)
- { // MS Excel fails to recognize tab-separated data columns
- // unless the initial header rows also contain at least one tab per row,
- // so add that to comment
- super("#\t", model, start, end, source, optimize_parameter, filename, error_handler, unixTimeStamp);
+ {
+ super("# ", model, start, end, source, optimize_parameter, filename, error_handler, unixTimeStamp);
this.formatter = formatter;
}
@Override
- protected void printExportInfo(final PrintStream out)
+ protected void printExportInfo(final PrintStream out) throws Exception
{
super.printExportInfo(out);
out.println(comment + "Format : " + formatter.toString());
diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/SpreadsheetExportJob.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/SpreadsheetExportJob.java
index c759089233..dedb3894fd 100644
--- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/SpreadsheetExportJob.java
+++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/export/SpreadsheetExportJob.java
@@ -66,7 +66,7 @@ protected void performExport(final JobMonitor monitor,
out.print(Messages.Export_Delimiter + item.getResolvedName() + " " + formatter.getHeader());
out.println();
- // Create speadsheet interpolation
+ // Create spreadsheet interpolation
final List iters = new ArrayList<>();
for (ModelItem item : model.getItems())
{
diff --git a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/export/ExportView.java b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/export/ExportView.java
index 5f25d10fb8..48d9fcf000 100644
--- a/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/export/ExportView.java
+++ b/app/databrowser/src/main/java/org/csstudio/trends/databrowser3/ui/export/ExportView.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2010-2018 Oak Ridge National Laboratory.
+ * Copyright (c) 2010-2025 Oak Ridge National Laboratory.
* 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
@@ -15,6 +15,7 @@
import org.csstudio.trends.databrowser3.Activator;
import org.csstudio.trends.databrowser3.Messages;
+import org.csstudio.trends.databrowser3.export.ExcelExportJob;
import org.csstudio.trends.databrowser3.export.ExportJob;
import org.csstudio.trends.databrowser3.export.MatlabFileExportJob;
import org.csstudio.trends.databrowser3.export.MatlabScriptExportJob;
@@ -88,6 +89,7 @@ public class ExportView extends VBox
format_digits = new TextField(Messages.ExportDefaultDigits),
filename = new TextField();
private final RadioButton source_raw = new RadioButton(Source.RAW_ARCHIVE.toString()),
+ type_excel = new RadioButton(Messages.ExportTypeExcel),
type_matlab = new RadioButton(Messages.ExportTypeMatlab);
private final CheckBox useUnixTimeStamp = new CheckBox(Messages.UseUnixTimeStamp);
@@ -190,7 +192,7 @@ public ExportView(final Model model)
// * Format *
- // (*) Spreadsheet ( ) Matlab
+ // (*) Excel ( ) Spreadsheet ( ) Matlab
// [x] Tabular [x] ... with min/max column [x] ... with Severity/Status
// (*) Default format ( ) decimal notation ( ) exponential notation _digits_ fractional digits
grid = new GridPane();
@@ -198,16 +200,20 @@ public ExportView(final Model model)
grid.setVgap(5);
grid.setPadding(new Insets(5));
+ type_excel.setTooltip(new Tooltip(Messages.ExportTypeExcelTT));
+ type_excel.setToggleGroup(table_types);
+ grid.add(type_excel, 0, 0);
+
final RadioButton type_spreadsheet = new RadioButton(Messages.ExportTypeSpreadsheet);
type_spreadsheet.setTooltip(new Tooltip(Messages.ExportTypeSpreadsheetTT));
type_spreadsheet.setToggleGroup(table_types);
- grid.add(type_spreadsheet, 0, 0);
+ grid.add(type_spreadsheet, 1, 0);
type_matlab.setTooltip(new Tooltip(Messages.ExportTypeMatlabTT));
type_matlab.setToggleGroup(table_types);
- grid.add(type_matlab, 1, 0);
+ grid.add(type_matlab, 2, 0);
- type_spreadsheet.setSelected(true);
+ type_excel.setSelected(true);
tabular.setTooltip(new Tooltip(Messages.ExportTabularTT));
tabular.setSelected(true);
@@ -250,10 +256,10 @@ public ExportView(final Model model)
grid.add(format_digits, 3, 2);
// Formatting only applies to spreadsheet
- format_default.disableProperty().bind(type_matlab.selectedProperty());
- format_decimal.disableProperty().bind(type_matlab.selectedProperty());
- format_expo.disableProperty().bind(type_matlab.selectedProperty());
- format_digits.disableProperty().bind(type_matlab.selectedProperty());
+ format_default.disableProperty().bind(type_matlab.selectedProperty().or(type_excel.selectedProperty()));
+ format_decimal.disableProperty().bind(type_matlab.selectedProperty().or(type_excel.selectedProperty()));
+ format_expo.disableProperty().bind(type_matlab.selectedProperty().or(type_excel.selectedProperty()));
+ format_digits.disableProperty().bind(type_matlab.selectedProperty().or(type_excel.selectedProperty()));
grid.add(new Label(Messages.ExportDigits), 4, 2);
@@ -371,7 +377,7 @@ else if (source == Source.LINEAR_INTERPOLATION)
}
// Get remaining export parameters
- final String filename = this.filename.getText().trim();
+ String filename = this.filename.getText().trim();
if (filename.isEmpty())
{
ExceptionDetailsErrorDialog.openError(this.filename, Messages.Error, Messages.ExportEnterFilenameError, new Exception(filename));
@@ -395,7 +401,31 @@ else if (source == Source.LINEAR_INTERPOLATION)
// Construct appropriate export job
final ExportJob export;
final TimeInterval start_end = range.toAbsoluteInterval();
- if (type_matlab.isSelected())
+ if (type_excel.isSelected())
+ { // Excel file export
+ if (! filename.endsWith(".xls"))
+ {
+ final Alert dialog = new Alert(AlertType.CONFIRMATION);
+ dialog.setTitle(Messages.ExportTypeExcel);
+ dialog.setHeaderText(Messages.ExportTypeExcelFilenamePrompt);
+ DialogHelper.positionDialog(dialog, this.filename, -200, -200);
+ ButtonType result = dialog.showAndWait().orElse(ButtonType.CANCEL);
+ if (result == ButtonType.CANCEL)
+ return;
+ if (result == ButtonType.OK)
+ {
+ int last = filename.lastIndexOf('.');
+ if (last == -1)
+ filename = filename + ".xls";
+ else
+ filename = filename.substring(0, last) + ".xls";
+ }
+ }
+ export = new ExcelExportJob(model, start_end.getStart(), start_end.getEnd(), source,
+ tabular.isSelected(), min_max_col.isSelected(), sev_stat.isSelected(),
+ optimize_parameter, filename, this::handleError, unixTimeStamp.get());
+ }
+ else if (type_matlab.isSelected())
{ // Matlab file export
if (filename.endsWith(".m"))
export = new MatlabScriptExportJob(model,
diff --git a/app/databrowser/src/main/resources/org/csstudio/trends/databrowser3/messages.properties b/app/databrowser/src/main/resources/org/csstudio/trends/databrowser3/messages.properties
index 1685c9de68..17da45c89f 100644
--- a/app/databrowser/src/main/resources/org/csstudio/trends/databrowser3/messages.properties
+++ b/app/databrowser/src/main/resources/org/csstudio/trends/databrowser3/messages.properties
@@ -98,9 +98,12 @@ ExportSource_RawArchiveTT=Fetch Raw Archived Data from archive
ExportStartExport=Export
ExportTabular=Tabular
ExportTabularTT=Generate Spreadsheet-type table for all channels, or list samples channels-by-channel?
-ExportTypeMatlab=Matlab
+ExportTypeExcel=Excel (*.xls)
+ExportTypeExcelFilenamePrompt=File name must be *.xls. Update file extension to .xls?
+ExportTypeExcelTT=Create Excel (*.xls) file
+ExportTypeMatlab=Matlab (*.m, *.mat)
ExportTypeMatlabTT=Create Matlab text file (*.m) or binary data (*.mat) file
-ExportTypeSpreadsheet=Spreadsheet
+ExportTypeSpreadsheet=Spreadsheet (*.dat, *.csv, *.txt, ...)
ExportTypeSpreadsheetTT=Create text file suitable for spreadsheet
ExportValueInfo=... with Severity/Status
ExportValueInfoTT=Include sample Severity/Status in output
diff --git a/app/databrowser/src/main/resources/org/csstudio/trends/databrowser3/messages_fr.properties b/app/databrowser/src/main/resources/org/csstudio/trends/databrowser3/messages_fr.properties
index dcbfa60a42..4e56eb9190 100644
--- a/app/databrowser/src/main/resources/org/csstudio/trends/databrowser3/messages_fr.properties
+++ b/app/databrowser/src/main/resources/org/csstudio/trends/databrowser3/messages_fr.properties
@@ -98,9 +98,12 @@ ExportSource_RawArchiveTT=R\u00E9cup\u00E9rer les donn\u00E9es archiv\u00E9es br
ExportStartExport=Exporter
ExportTabular=Tabulaire
ExportTabularTT=G\u00E9n\u00E9rer un tableau de type feuille de calcul pour tous les canaux, ou lister les \u00E9chantillons canal par canal ?
-ExportTypeMatlab=Matlab
+ExportTypeExcel=Excel (*.xls)
+ExportTypeExcelFilenamePrompt=File name must be *.xls. Update file extension to .xls?
+ExportTypeExcelTT=Create Excel (*.xls) file
+ExportTypeMatlab=Matlab (*.m, *.mat)
ExportTypeMatlabTT=Cr\u00E9er un fichier texte Matlab (*.m) ou un fichier de donn\u00E9es binaire (*.mat)
-ExportTypeSpreadsheet=Feuille de calcul
+ExportTypeSpreadsheet=Feuille de calcul (*.dat, *.csv, *.txt, ...)
ExportTypeSpreadsheetTT=Cr\u00E9er un fichier texte adapt\u00E9 \u00E0 une feuille de calcul
ExportValueInfo=... avec S\u00E9v\u00E9rit\u00E9/Statut
ExportValueInfoTT=Inclure la s\u00E9v\u00E9rit\u00E9/le statut de l\u0027\u00E9chantillon dans la sortie
diff --git a/dependencies/phoebus-target/.classpath b/dependencies/phoebus-target/.classpath
index b40a52a677..5b5a81fc69 100644
--- a/dependencies/phoebus-target/.classpath
+++ b/dependencies/phoebus-target/.classpath
@@ -1,12 +1,5 @@
-
-
-
-
-
-
-
@@ -44,17 +37,19 @@
+
+
-
+
@@ -70,6 +65,7 @@
+
@@ -101,7 +97,9 @@
-
+
+
+
@@ -115,7 +113,6 @@
-
@@ -131,6 +128,7 @@
+
@@ -147,6 +145,7 @@
+
@@ -192,20 +191,20 @@
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dependencies/phoebus-target/check_classpath.py b/dependencies/phoebus-target/check_classpath.py
new file mode 100755
index 0000000000..fed011c11e
--- /dev/null
+++ b/dependencies/phoebus-target/check_classpath.py
@@ -0,0 +1,37 @@
+#!/bin/env python3
+
+import argparse
+from os import path
+from glob import glob
+import re
+import xml.etree.ElementTree as ET
+
+
+parser = argparse.ArgumentParser(
+ description='Check if .classpath entries exist. If not, suggest alternate version')
+args = parser.parse_args()
+
+
+xml = ET.parse(".classpath")
+root = xml.getroot()
+
+for entry in root.iter('classpathentry'):
+ if entry.get('kind') == 'lib':
+ jar = entry.get('path')
+ if path.exists(jar):
+ pass
+ else:
+ pattern = re.sub(r'[0-9.]+', r'*', jar)
+ # print(pattern)
+ alt = glob(pattern)
+ # print(alt)
+ if len(alt) == 1:
+ update = alt[0]
+ print("%-60s -----> use %s" % (jar, update))
+ elif len(alt) > 1:
+ update = " or ".join(alt)
+ print("%-60s -----> use %s" % (jar, update))
+ else:
+ print("%-60s is missing, no replacement found" % jar)
+
+
diff --git a/dependencies/phoebus-target/pom.xml b/dependencies/phoebus-target/pom.xml
index 70cbea4cf5..405f674ba2 100644
--- a/dependencies/phoebus-target/pom.xml
+++ b/dependencies/phoebus-target/pom.xml
@@ -511,7 +511,39 @@
epics-jackie-client
3.1.0
-
+
+
+
+ org.apache.poi
+ poi
+
+
+ com.zaxxer
+ SparseBitSet
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.slf4j
+ jcl-over-slf4j
+
+
+ commons-codec
+ commons-codec
+
+
+ org.apache.commons
+ commons-collections4
+
+
+ org.apache.commons
+ commons-math3
+
+
+ 5.0.0
+