package io.gitlab.jfronny.ImgJava.util; import org.jdom.Document; import org.jdom.Element; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Scanner; /** * Die Klasse Table vereinfacht den Zugriff auf CSV-Dateien. * Die Klassen Table und TableRow ermöglichen einen einfachen Zugriff auf tabellenbasierte * Dokumente. * * @author Thomas Schaller * @version 1.0 vom 01.02.2019 */ public class Table { // Standardtrennzeichen für Spalten private static final char DEFAULT_SEPARATOR = ';'; // Standardmarkierung für Texte private static final char DEFAULT_QUOTE = '"'; // Standardtrennzeichen für Dezimalzahlen private static final char DEFAULT_COMMA = ','; // mögliche Spaltentypen private static final String UNKNOWN = "UNKOWN"; private static final String INT = "INTEGER"; private static final String DOUBLE = "DOUBLE"; private static final String FLOAT = "FLOAT"; // interne Verwaltung des Dokuments als JDOM-Document-Objekt private Document doc; // Verweis auf Element für Kopfzeile private Element header; // Ende Attribute /** * Erzeugt leeres Tabellen-Dokument. */ public Table() { this.doc = new Document(); doc.setRootElement(new Element("CSV-Data")); this.header = new Element("Header"); doc.getRootElement().addContent(header); } /** * Erzeugt Tabellen-Dokument aus einer CSV-Datei. * Liest den Inhalt einer Datei und erstellt ein Tabellenobjekt mit seinen Werten. * Wenn die Datei eine Kopfzeile enthält, fügen Sie "header" in den Parameter options ein. Wenn die Datei keine Kopfzeile hat, * dann lassen Sie einfach die Option "header" weg. * @param file Dateiname der CSV-Datei. * @param options Geben Sie hier "header" an, wenn die Datei eine Kopfzeile enthält. * @param separator Trennzeichen für Spalten (meist ';' oder ',' oder '\t' für Tab) * @param quote Kennung für Texte (meist '"'). */ public Table(Path file, String options, char separator, char quote) throws IOException { loadCSV(file, options, separator, quote); } /** * Erzeugt Tabellen-Dokument aus einer CSV-Datei. * Liest den Inhalt einer Datei und erstellt ein Tabellenobjekt mit seinen Werten (Separator = ';', Kennung für Text = '"'). * Wenn die Datei eine Kopfzeile enthält, fügen Sie "header" in den Parameter options ein. Wenn die Datei keine Kopfzeile hat, * dann lassen Sie einfach die Option "header" weg. * @param file Dateiname der CSV-Datei. * @param options Geben Sie hier "header" an, wenn die Datei eine Kopfzeile enthält. */ public Table(Path file, String options) throws IOException { loadCSV(file, options); } /** * Erzeugt Tabellen-Dokument aus einer CSV-Datei. * Liest den Inhalt einer Datei ohne Kopfzeile und erstellt ein Tabellenobjekt mit seinen Werten (Separator = ';', Kennung für Text = '"'). * @param file Dateiname der CSV-Datei. */ public Table(Path file) throws IOException { loadCSV(file); } // Anfang Methoden /** * Liest den Inhalt einer CSV-Datei ohne Kopfzeile (Separator = ';', Kennung für Text = '"'). * @param file Dateiname der CSV-Datei. */ public void loadCSV(Path file) throws IOException { loadCSV(file, ""); } /** * Liest den Inhalt einer CSV-Datei (Separator = ';', Kennung für Text = '"'). * Wenn die Datei eine Kopfzeile enthält, fügen Sie "header" in den Parameter options ein. Wenn die Datei keine Kopfzeile hat, * dann lassen Sie einfach die Option "header" weg. * @param file Dateiname der CSV-Datei. * @param options Geben Sie hier "header" an, wenn die Datei eine Kopfzeile enthält. */ public void loadCSV(Path file, String options) throws IOException { loadCSV(file, options, DEFAULT_SEPARATOR, DEFAULT_QUOTE); } /** * Liest den Inhalt einer CSV-Datei. * Wenn die Datei eine Kopfzeile enthält, fügen Sie "header" in den Parameter options ein. Wenn die Datei keine Kopfzeile hat, * dann lassen Sie einfach die Option "header" weg. * @param file Dateiname der CSV-Datei. * @param options Geben Sie hier "header" an, wenn die Datei eine Kopfzeile enthält. * @param separator Trennzeichen für Spalten (meist ';' oder ',' oder '\t' für Tab) * @param quote Kennung für Texte (meist '"'). */ public void loadCSV(Path file, String options, char separator, char quote) throws IOException { doc = new Document(); doc.setRootElement(new Element("CSV-Data")); header = new Element("Header"); doc.getRootElement().addContent(header); Scanner scanner = new Scanner(file); if (options.toLowerCase().contains("header") && scanner.hasNext()) { List entries = parseLine(scanner.nextLine(), separator, quote); int i = 0; for (String s : entries) { Element entry = new Element("Column"); header.addContent(entry); entry.setText(s); entry.setAttribute("type", "unknown"); i++; } } List cols = header.getChildren(); while (scanner.hasNext()) { Element line = new Element("Row"); doc.getRootElement().addContent(line); List entries = parseLine(scanner.nextLine(), separator, quote); int i = 0; for (String s : entries) { if (i == cols.size()) { Element entry = new Element("Column"); entry.setAttribute("type", "unknown"); header.addContent(entry); cols = header.getChildren(); } Element entry = new Element("Entry"); entry.setText(s); line.addContent(entry); i++; } } scanner.close(); } /** * Speichert das aktuelle Dokument als CSV-Datei ohne Kopfzeile (Separator = ';', Kennung für Text = '"'). * @param file Dateiname der CSV-Datei. */ public void saveCSV(Path file) throws IOException { saveCSV(file, ""); } /** * Speichert das aktuelle Dokument als CSV-Datei (Separator = ';', Kennung für Text = '"'). * Wenn die Datei eine Kopfzeile enthalten, fügen Sie "header" in den Parameter options ein. Wenn die Datei keine Kopfzeile haben soll, * dann lassen Sie einfach die Option "header" weg. * @param options Geben Sie hier "header" an, wenn die Datei eine Kopfzeile haben soll. * @param file Dateiname der CSV-Datei. */ public void saveCSV(Path file, String options) throws IOException { saveCSV(file, options, DEFAULT_SEPARATOR, DEFAULT_QUOTE); } /** * Speichert das aktuelle Dokument als CSV-Datei. * Wenn die Datei eine Kopfzeile enthalten, fügen Sie "header" in den Parameter options ein. Wenn die Datei keine Kopfzeile haben soll, * dann lassen Sie einfach die Option "header" weg. * @param options Geben Sie hier "header" an, wenn die Datei eine Kopfzeile haben soll. * @param file Dateiname der CSV-Datei. * @param separator Trennzeichen für Spalten (meist ';' oder ',' oder '\t' für Tab) * @param quote Kennung für Texte (meist '"'). */ public void saveCSV(Path file, String options, char separator, char quote) throws IOException { PrintStream outputFile = new PrintStream(Files.newOutputStream(file)); System.out.println("Speicher in : " + file.toAbsolutePath()); List columns = header.getChildren(); String sq = "" + quote; String ss = "" + separator; if (quote == '"') sq = "\""; if (separator == '"') ss = "\""; if (options.toLowerCase().contains("header")) { String h = ""; for (Element c : columns) { h += ss + sq + c.getText() + sq; } outputFile.println(h.substring(1)); } for (int i = 0; i < getRowCount(); i++) { String l = ""; for (String s : getStringRow(i)) { if (s.contains("" + separator)) { if (quote == '"' && s.contains("\"")) { s = s.replace("\"", "\"\""); } l += ss + sq + s + sq; } else { l += ss + s; } } outputFile.println(l.substring(1)); } outputFile.close(); } /** Speichert die Tabelle als XML-Dokument. * @param file Dateiname des XML-Files */ public void saveXML(Path file) throws IOException { // new XMLOutputter().output(doc, System.out); XMLOutputter xmlOutput = new XMLOutputter(); // display nice nice xmlOutput.setFormat(Format.getPrettyFormat()); OutputStream outputFile = Files.newOutputStream(file); System.out.println("Speicher in : " + file.toAbsolutePath()); xmlOutput.output(doc, outputFile); outputFile.close(); System.out.println("File Saved!"); } //----------------------------------------------- Zeigerbewegungen -------------------------------------------------- /** HIlfsfunktion für die Analyse einer Dateizeile * @param cvsLine Zeile aus der Datei * @return Liste von String für die einzelnen Spalten */ private List parseLine(String cvsLine) { return parseLine(cvsLine, DEFAULT_SEPARATOR, DEFAULT_QUOTE); } /** HIlfsfunktion für die Analyse einer Dateizeile * @param cvsLine Zeile aus der Datei * @param separator Trennzeichen für die Spalten * @return Liste von String für die einzelnen Spalten */ private List parseLine(String cvsLine, char separator) { return parseLine(cvsLine, separator, DEFAULT_QUOTE); } /** HIlfsfunktion für die Analyse einer Dateizeile * @param cvsLine Zeile aus der Datei * @param separator Trennzeichen für die Spalten * @param customQuote Kennung für Strings * @return Liste von String für die einzelnen Spalten */ private List parseLine(String cvsLine, char separator, char customQuote) { List result = new ArrayList<>(); //if empty, return! if (cvsLine == null && cvsLine.isEmpty()) { return result; } //ggf. Default-Value laden if (customQuote == ' ') { customQuote = DEFAULT_QUOTE; } if (separator == ' ') { separator = DEFAULT_SEPARATOR; } StringBuffer curVal = new StringBuffer(); boolean inQuotes = false; boolean startCollectChar = false; boolean doubleQuotesInColumn = false; char[] chars = cvsLine.toCharArray(); for (char ch : chars) { if (inQuotes) { // aktueller Text ist in Quotes eingeschlossen startCollectChar = true; if (ch == customQuote) { // Quotes werden beendet, aber Achtung bei "" => Metazeichen inQuotes = false; if (ch == '\"') { doubleQuotesInColumn = true; } } else { if (ch == '\"' && !doubleQuotesInColumn) { doubleQuotesInColumn = true; } else { curVal.append(ch); doubleQuotesInColumn = false; } } } else { if (ch == customQuote) { inQuotes = true; //Fixed : allow "" in empty quote enclosed if (ch == '\"') { if (doubleQuotesInColumn) { curVal.append('"'); doubleQuotesInColumn = false; } else doubleQuotesInColumn = true; } } else { doubleQuotesInColumn = false; if (ch == separator) { result.add(curVal.toString()); curVal = new StringBuffer(); startCollectChar = false; } else if (ch == '\r') { //ignore LF characters continue; } else if (ch == '\n') { //the end, break! break; } else { curVal.append(ch); } } } } result.add(curVal.toString()); return result; } /** * Sucht die Nummer einer durch Namen gegebenen Spalte. * @param name Name der Spalte * @return Nummer der Spalte */ private int findColumnNumber(String name) { List columns = header.getChildren(); int i = 0; for (Element c : columns) { if (c.getText().equalsIgnoreCase(name)) { return i; } i++; } return -1; } /** * Fügt eine neue Spalte am Ende der Tabelle an. */ public void addColumn() { Element entry = new Element("Column"); entry.setAttribute("type", Table.UNKNOWN); header.addContent(entry); } /** * Fügt eine neue Spalte am Ende der Tabelle an und benennt sie. * @param title Bezeichnung der Spalte */ public void addColumn(String title) { addColumn(); Element nc = ((List) (header.getChildren())).get(header.getChildren().size() - 1); nc.setText(title); } /** * Fügt eine neue Spalte am Ende der Tabelle an und benennt und typisiert sie. * @param title Bezeichnung der Spalte * @param type Typ der Spalte (UNKNOWN, DOUBLE, INTEGER, FLOAT) */ public void addColumn(String title, String type) { addColumn(title); Element nc = ((List) (header.getChildren())).get(header.getChildren().size() - 1); nc.setAttribute("type", type); } /** * Löscht eine Spalte. * @param i Nummer der Spalte. */ public void removeColumn(int i) { List lines = doc.getRootElement().getChildren(); for (Element l : lines) { if (l.getChildren().size() > i) l.removeContent(i); } } /** * Löscht eine Spalte * @param name Name der Spalte */ public void removeColumn(String name) { removeColumn(findColumnNumber(name)); } /** * Liefert die Anzahl der Spalten in der Tabelle * @return Anzahl der Spalten */ public int getColumnCount() { return header.getChildren().size(); } /** * Liefert die Anzahl der Zeilen in der Tabelle * @return Anzahl der Zeilen */ public int getRowCount() { return doc.getRootElement().getChildren().size() - 1; } /** * Löscht alle Zeilen der Tabelle. * Die Spaltenüberschriften und Typen bleiben erhalten. */ public void clearRows() { doc.getRootElement().removeChildren("Row"); } /** * Fügt eine neue Zeile an das Ende der Tabelle an. * @return ein TableRow-Objekt für diese neue Zeile */ public TableRow addRow() { Element row = new Element("Row"); doc.getRootElement().addContent(row); return new TableRow(doc, row); } /** * Löscht eine Zeile * @param i Nummer der Zeile */ public void removeRow(int i) { if (i < getRowCount()) { doc.getRootElement().removeContent(i); } } /** * Liefert eine Zeile der Tabelle * @param i Nummer der Zeile * @return TableRow-Objekt für diese Zeile */ public TableRow getRow(int i) { if (i < getRowCount()) { List rows = doc.getRootElement().getChildren(); return new TableRow(doc, rows.get(i + 1)); } return null; } /** * Liefert die ganze Tabelle als Array von TableRow-Objekten * @return Array von TableRow-Objekten */ public TableRow[] rows() { TableRow[] rows = new TableRow[getRowCount()]; for (int i = 0; i < getRowCount(); i++) { rows[i] = getRow(i); } return rows; } /** * Liefert den Wert einer Zelle als Integer-Zahl * @param row Zeilennummer * @param column Spaltennummer * @return Wert der Zelle */ public int getInt(int row, int column) { return getRow(row).getInt(column); } /** * Liefert den Wert einer Zelle als Integer-Zahl * @param row Zeilennummer * @param name Name der Spalte * @return Wert der Zelle */ public int getInt(int row, String name) { return getRow(row).getInt(name); } /** * Setzt den Wert einer Zelle als Integer-Zahl * @param row Zeilennummer * @param column Spaltennummer * @param value neuer Wert der Zelle */ public void setInt(int row, int column, int value) { getRow(row).setInt(column, value); } /** * Setzt den Wert einer Zelle als Integer-Zahl * @param row Zeilennummer * @param name Name der Spalte * @param value neuer Wert der Zelle */ public void setInt(int row, String name, int value) { getRow(row).setInt(name, value); } /** * Liefert alle Werte einer Zeile als Integer-Array. * @param row Nummer der Zeile * @return int-Array, dass alle Werte der Zeile enthält */ public int[] getIntRow(int row) { try { TableRow trow = getRow(row); int anz = getColumnCount(); int[] r = new int[anz]; for (int i = 0; i < anz; i++) { r[i] = trow.getInt(i); } return r; } catch (Exception e) { return null; } } /** * Liefert alle Werte einer Spalte als Integer-Array. * @param column Nummer der Spalte * @return int-Array, dass alle Werte der Spalte enthält */ public int[] getIntColumn(int column) { try { int anz = getRowCount(); int[] r = new int[anz]; for (int i = 0; i < anz; i++) { r[i] = getInt(i, column); } return r; } catch (Exception e) { return null; } } /** * Liefert alle Werte einer Spalte als Integer-Array. * @param name Name der Spalte * @return int-Array, dass alle Werte der Spalte enthält */ public int[] getIntColumn(String name) { return getIntColumn(findColumnNumber(name)); } /** * Liefert den Wert einer Zelle als Float-Zahl * @param row Zeilennummer * @param column Spaltennummer * @return Wert der Zelle */ public float getFloat(int row, int column) { return getRow(row).getFloat(column); } /** * Liefert den Wert einer Zelle als Float-Zahl * @param row Zeilennummer * @param name Name der Spalte * @return Wert der Zelle */ public float getFloat(int row, String name) { return getRow(row).getFloat(name); } /** * Setzt den Wert einer Zelle als Float-Zahl * @param row Zeilennummer * @param column Spaltennummer * @param value neuer Wert der Zelle */ public void setFloat(int row, int column, float value) { getRow(row).setFloat(column, value); } /** * Setzt den Wert einer Zelle als Float-Zahl * @param row Zeilennummer * @param name Name der Spalte * @param value neuer Wert der Zelle */ public void setFloat(int row, String name, float value) { getRow(row).setFloat(name, value); } /** * Liefert alle Werte einer Zeile als Float-Array. * @param row Nummer der Zeile * @return int-Array, dass alle Werte der Zeile enthält */ public float[] getFloatRow(int row) { try { TableRow trow = getRow(row); int anz = getColumnCount(); float[] r = new float[anz]; for (int i = 0; i < anz; i++) { r[i] = trow.getFloat(i); } return r; } catch (Exception e) { return null; } } /** * Liefert alle Werte einer Spalte als Float-Array. * @param column Nummer der Spalte * @return int-Array, dass alle Werte der Spalte enthält */ public float[] getFloatColumn(int column) { try { int anz = getRowCount(); float[] r = new float[anz]; for (int i = 0; i < anz; i++) { r[i] = getFloat(i, column); } return r; } catch (Exception e) { return null; } } /** * Liefert alle Werte einer Spalte als Float-Array. * @param name Name der Spalte * @return int-Array, dass alle Werte der Spalte enthält */ public float[] getFloatColumn(String name) { return getFloatColumn(findColumnNumber(name)); } /** * Liefert den Wert einer Zelle als Double-Zahl * @param row Zeilennummer * @param column Spaltennummer * @return Wert der Zelle */ public double getDouble(int row, int column) { return getRow(row).getDouble(column); } /** * Liefert den Wert einer Zelle als Double-Zahl * @param row Zeilennummer * @param name Name der Spalte * @return Wert der Zelle */ public double getDouble(int row, String name) { return getRow(row).getDouble(name); } /** * Setzt den Wert einer Zelle als Double-Zahl * @param row Zeilennummer * @param column Spaltennummer * @param value neuer Wert der Zelle */ public void setDouble(int row, int column, double value) { getRow(row).setDouble(column, value); } /** * Setzt den Wert einer Zelle als Double-Zahl * @param row Zeilennummer * @param name Name der Spalte * @param value neuer Wert der Zelle */ public void setDouble(int row, String name, double value) { getRow(row).setDouble(name, value); } /** * Liefert alle Werte einer Spalte als Double-Array. * @param row Nummer der Spalte * @return int-Array, dass alle Werte der Spalte enthält */ public double[] getDoubleRow(int row) { try { TableRow trow = getRow(row); int anz = getColumnCount(); double[] r = new double[anz]; for (int i = 0; i < anz; i++) { r[i] = trow.getDouble(i); } return r; } catch (Exception e) { return null; } } /** * Liefert alle Werte einer Spalte als Double-Array. * @param column Nummer der Spalte * @return int-Array, dass alle Werte der Spalte enthält */ public double[] getDoubleColumn(int column) { try { int anz = getRowCount(); double[] r = new double[anz]; for (int i = 0; i < anz; i++) { r[i] = getDouble(i, column); } return r; } catch (Exception e) { return null; } } /** * Liefert alle Werte einer Spalte als Double-Array. * @param name Name der Spalte * @return int-Array, dass alle Werte der Spalte enthält */ public double[] getDoubleColumn(String name) { return getDoubleColumn(findColumnNumber(name)); } /** * Liefert den Wert einer Zelle als String * @param row Zeilennummer * @param column Spaltennummer * @return Wert der Zelle */ public String getString(int row, int column) { return getRow(row).getString(column); } /** * Liefert den Wert einer Zelle als String * @param row Zeilennummer * @param name Name der Spalte * @return Wert der Zelle */ public String getString(int row, String name) { return getRow(row).getString(name); } /** * Setzt den Wert einer Zelle als String * @param row Zeilennummer * @param column Spaltennummer * @param text neuer Wert der Zelle */ public void setString(int row, int column, String text) { getRow(row).setString(column, text); } /** * Setzt den Wert einer Zelle als String * @param row Zeilennummer * @param name Name der Spalte * @param text neuer Wert der Zelle */ public void setString(int row, String name, String text) { getRow(row).setString(name, text); } /** * Liefert alle Werte einer Spalte als String-Array. * @param row Nummer der Spalte * @return int-Array, dass alle Werte der Spalte enthält */ public String[] getStringRow(int row) { try { TableRow trow = getRow(row); int anz = getColumnCount(); String[] r = new String[anz]; for (int i = 0; i < anz; i++) { r[i] = trow.getString(i); } return r; } catch (Exception e) { return null; } } /** * Liefert alle Werte einer Spalte als String-Array. * @param column Nummer der Spalte * @return int-Array, dass alle Werte der Spalte enthält */ public String[] getStringColumn(int column) { try { int anz = getRowCount(); String[] r = new String[anz]; for (int i = 0; i < anz; i++) { r[i] = getString(i, column); } return r; } catch (Exception e) { return null; } } /** * Liefert alle Werte einer Spalte als String-Array. * @param name Name der Spalte * @return int-Array, dass alle Werte der Spalte enthält */ public String[] getStringColumn(String name) { return getStringColumn(findColumnNumber(name)); } /** * Sucht nach einem bestimmtem Wert in einer Zeile. * @param value Wert der gesucht werden soll * @param column Nummer der Spalte, die durchsucht werden soll * @return TableRow-Objekt der Zeile, wenn der Wert gefunden wurde, sonst null */ public TableRow findRow(String value, int column) { for (int i = 0; i < getRowCount(); i++) { if (getString(i, column).equals(value)) { return getRow(i); } } return null; } /** * Sucht nach einem bestimmtem Wert in einer Zeile. * @param value Wert der gesucht werden soll * @param name Name der Spalte, die durchsucht werden soll * @return TableRow-Objekt der Zeile, wenn der Wert gefunden wurde, sonst null */ public TableRow findRow(String value, String name) { return findRow(value, findColumnNumber(name)); } /** * Kürzt alle Einträge der Tabelle um unnötige Leerzeichen am Anfang oder Ende */ public void trim() { for (int y = 0; y < getRowCount(); y++) { for (int x = 0; x < getColumnCount(); x++) { setString(y, x, getString(y, x).trim()); } } } }