diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableToCSV.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableToCSV.java index e802fe9d34..4876a27d5a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableToCSV.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableToCSV.java @@ -35,18 +35,25 @@ public final class GTableToCSV { final static String TITLE = "Export to CSV"; public final static void writeCSV(File file, GTable table) { - ConvertTask task = new ConvertTask(file, table, table.getModel()); + ConvertTask task = new ConvertTask(file, table); new TaskLauncher(task, table, 0); } public final static void writeCSVUsingColunns(File file, GTable table, List selectedColumns) { - ConvertTask task = new ConvertTask(file, table, table.getModel(), selectedColumns); + ConvertTask task = new ConvertTask(file, table, selectedColumns); new TaskLauncher(task, table, 0); } - private final static void writeCSV(File file, GTable table, GTableColumnModel columnModel, - TableModel model, List columns, TaskMonitor monitor) throws IOException { + final static void writeCSV(File file, GTable table, + List columns, TaskMonitor monitor) throws IOException { + + PrintWriter writer = new PrintWriter(file); + writeCSV(writer, table, columns, monitor); + } + + final static void writeCSV(PrintWriter writer, GTable table, + List columns, TaskMonitor monitor) { List tableColumns = null; if (columns.isEmpty()) { @@ -56,7 +63,7 @@ public final class GTableToCSV { tableColumns = getTableColumnsByIndex(table, columns); } - PrintWriter writer = new PrintWriter(file); + TableModel model = table.getModel(); try { writeColumnNames(writer, tableColumns, model, monitor); writeNewLine(writer); @@ -142,7 +149,8 @@ public final class GTableToCSV { final int column) { final String[] result = new String[1]; try { - SwingUtilities.invokeAndWait(() -> result[0] = getTableCellValue(table, model, row, column)); + SwingUtilities + .invokeAndWait(() -> result[0] = getTableCellValue(table, model, row, column)); } catch (InterruptedException e) { return null; @@ -301,14 +309,19 @@ public final class GTableToCSV { *

* Note: when importing into Excel, the quotes are stripped off. */ - private final static void writeField(PrintWriter writer, String fieldValue, TaskMonitor monitor) { + private final static void writeField(PrintWriter writer, String fieldValue, + TaskMonitor monitor) { writer.print("\""); for (int i = 0; i < fieldValue.length(); ++i) { if (monitor.isCancelled()) { break; } + if (fieldValue.charAt(i) == '"') {//embedded separator - writer.print("\""); + writer.print("\\\""); + } + else if (fieldValue.charAt(i) == ',') { + writer.print("\\,"); } else { writer.print(fieldValue.charAt(i)); @@ -319,36 +332,30 @@ public final class GTableToCSV { private static class ConvertTask extends Task { private final GTable table; - private TableModel model; - private GTableColumnModel columnModel; private File file; private List columns = new ArrayList(); - ConvertTask(File file, GTable table, TableModel model) { + ConvertTask(File file, GTable table) { super(GTableToCSV.TITLE, true, true, true); this.file = file; this.table = table; - this.columnModel = (GTableColumnModel) table.getColumnModel(); - this.model = model; } - ConvertTask(File file, GTable table, TableModel model, List columns) { + ConvertTask(File file, GTable table, List columns) { super(GTableToCSV.TITLE, true, true, true); this.file = file; this.table = table; this.columns = columns; - this.columnModel = (GTableColumnModel) table.getColumnModel(); - this.model = model; } @Override public void run(TaskMonitor monitor) { try { - GTableToCSV.writeCSV(file, table, columnModel, model, columns, monitor); + GTableToCSV.writeCSV(file, table, columns, monitor); } catch (IOException e) { - Msg.error(GTable.class.getName(), e.getMessage()); + Msg.error(GTableToCSV.class.getName(), e.getMessage()); } DockingWindowManager manager = DockingWindowManager.getInstance(table); diff --git a/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/GTableCSVTest.java b/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/GTableCSVTest.java new file mode 100644 index 0000000000..6b89463670 --- /dev/null +++ b/Ghidra/Framework/Docking/src/test/java/docking/widgets/table/GTableCSVTest.java @@ -0,0 +1,145 @@ +/* ### + * IP: GHIDRA + * + * 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 docking.widgets.table; + +import static org.junit.Assert.*; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.*; + +import org.junit.Test; + +import ghidra.util.task.TaskMonitor; + +public class GTableCSVTest { + + @Test + public void testCSV_QuotesGetEscaped() { + + AnyObjectTableModel model = + new AnyObjectTableModel<>("MyModel", CSVRowObject.class, + "getName", "getDescription", "getNumber"); + + //@formatter:off + List data = Arrays.asList( + new CSVRowObject("Bob", "Bobby", 11), + new CSVRowObject("Joan", "Joan has \"quoted\" text", 0), + new CSVRowObject("Sam", "\"Sam has a single quote text", 23), + new CSVRowObject("Time", "Tim is last", 33) + ); + //@formatter:on + model.setModelData(data); + + GTable table = new GTable(model); + List columns = new ArrayList<>(); + + PrintWriterSpy writer = new PrintWriterSpy(); + GTableToCSV.writeCSV(writer, table, columns, TaskMonitor.DUMMY); + + assertRowValues(data, writer); + } + + @Test + public void testCSV_CommasGetEscaped() { + + AnyObjectTableModel model = + new AnyObjectTableModel<>("MyModel", CSVRowObject.class, + "getName", "getDescription", "getNumber"); + + //@formatter:off + List data = Arrays.asList( + new CSVRowObject("Bob", "Bobby", 11), + new CSVRowObject("Joan", "Joan has a comma, in her text", 0), + new CSVRowObject("Sam", ",Sam has a leading comma", 23), + new CSVRowObject("Time", "Tim is last", 33) + ); + //@formatter:on + model.setModelData(data); + + GTable table = new GTable(model); + List columns = new ArrayList<>(); + + PrintWriterSpy writer = new PrintWriterSpy(); + GTableToCSV.writeCSV(writer, table, columns, TaskMonitor.DUMMY); + + assertRowValues(data, writer); + } + + private void assertRowValues(List data, PrintWriterSpy writer) { + + String results = writer.toString(); + String[] lines = results.split("\n"); + for (int i = 1; i < lines.length; i++) { + int index = i - 1; // the first line is the header + CSVRowObject row = data.get(index); + String line = lines[i]; + String[] columns = line.split("(?