diff --git a/src/main/java/rickwporter/prettytable/CsvTableRender.java b/src/main/java/rickwporter/prettytable/CsvTableRender.java new file mode 100644 index 0000000..74f3408 --- /dev/null +++ b/src/main/java/rickwporter/prettytable/CsvTableRender.java @@ -0,0 +1,32 @@ +package rickwporter.prettytable; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; + +class CsvTableRender implements TableRenderInterface { + + @Override + public String render(PrettyTable table, boolean removedRedundant) { + try { + StringBuilder result = new StringBuilder(); + CSVFormat format = CSVFormat.DEFAULT.builder().setRecordSeparator("\n").build(); + CSVPrinter printer = new CSVPrinter(result, format); + if (!table.getHeaders().isEmpty()) { + printer.printRecord(table.getHeaders()); + } + for (List row : table.getRows()) { + List current = row.stream().map(c -> c.toString()).collect(Collectors.toList()); + printer.printRecord(current); + } + printer.close(); + return result.toString(); + } catch (IOException ex) { + // nothing to do here + } + return null; + } +} diff --git a/src/main/java/rickwporter/prettytable/HtmlTableRender.java b/src/main/java/rickwporter/prettytable/HtmlTableRender.java new file mode 100644 index 0000000..ff91149 --- /dev/null +++ b/src/main/java/rickwporter/prettytable/HtmlTableRender.java @@ -0,0 +1,90 @@ +package rickwporter.prettytable; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import rickwporter.prettytable.PrettyTable.HorizontalAlign; +import rickwporter.prettytable.PrettyTable.OutputFormat; + +class HtmlTableRender implements TableRenderInterface { + private static final String HTML_TABLE_TAG = "table"; + private static final String HTML_HEADER_TAG = "thead"; + private static final String HTML_BODY_TAG = "tbody"; + private static final String HTML_ROW_TAG = "tr"; + private static final String HTML_CELL_BODY_TAG = "td"; + private static final String HTML_CELL_HEADER_TAG = "th"; + + String htmlColumnFormat(Integer column, List hAligns) { + if (hAligns.isEmpty()) { + return ""; + } + return " style=\"" + hAligns.get(column).getHtmlStyle() + "\""; + } + + String htmlRow( + List row, + String initIndent, + String indent, + String cellTag, + List hAligns + ) { + String result = String.format("%s<%s>\n", initIndent, HTML_ROW_TAG); + for (int column = 0; column < row.size(); column++) { + Object obj = row.get(column); + String cellFormat = htmlColumnFormat(column, hAligns); + result += String.format( + "%s%s<%s%s>%s\n", initIndent, indent, cellTag, cellFormat, obj.toString(), cellTag + ); + } + result += String.format("%s\n", initIndent, HTML_ROW_TAG); + return result; + } + + @Override + public String render(PrettyTable table, boolean removeRedundant) { + StringBuilder result = new StringBuilder(); + String indent = " "; + List hAligns = table.getHorizAligns(); + + result.append(String.format("<%s>\n", HTML_TABLE_TAG)); + if (!table.getHeaders().isEmpty()) { + result.append(String.format("%s<%s>\n", indent, HTML_HEADER_TAG)); + result.append(htmlRow(table.getHeaders(), indent + indent, indent, HTML_CELL_HEADER_TAG, hAligns)); + result.append(String.format("%s\n", indent, HTML_HEADER_TAG)); + } + + result.append(String.format("%s<%s>\n", indent, HTML_BODY_TAG)); + List lastRow = new ArrayList<>(); + for (List row : table.getRows()) { + List currentRow = row.stream() + .map(c -> { + if (c instanceof PrettyTable) { + PrettyTable t = (PrettyTable) c; + if (t.getDefaultOutput() == OutputFormat.HTML) { + String out = "\n" + t.toHtml(true); + out = out.replaceAll("\n", "\n" + indent + indent + indent + indent); + return out.substring(0, out.lastIndexOf(indent)); + } + return t.toString().replace("\n", "
"); + } + return c.toString(); + }) + .collect(Collectors.toList()); + List fullRow = new ArrayList(currentRow); // make a copy before manipulating + if (removeRedundant) { + for (int i = 0; i < lastRow.size(); i++) { + if (!lastRow.get(i).equals(currentRow.get(i))) { + break; + } + currentRow.set(i, ""); + } + lastRow = fullRow; + } + result.append(htmlRow(currentRow, indent + indent, indent, HTML_CELL_BODY_TAG, hAligns)); + } + result.append(String.format("%s\n", indent, HTML_BODY_TAG)); + result.append(String.format("\n", HTML_TABLE_TAG)); + return result.toString(); + } +} diff --git a/src/main/java/rickwporter/prettytable/JsonTableRender.java b/src/main/java/rickwporter/prettytable/JsonTableRender.java new file mode 100644 index 0000000..871a476 --- /dev/null +++ b/src/main/java/rickwporter/prettytable/JsonTableRender.java @@ -0,0 +1,89 @@ +package rickwporter.prettytable; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + +import rickwporter.prettytable.PrettyTable.OutputFormat; + +class JsonTableRender implements TableRenderInterface { + String jsonEncode(Object object) { + if (object instanceof Integer) { + return object.toString(); + } + try { + Integer value = Integer.parseInt(object.toString()); + return value.toString(); + } catch (NumberFormatException ex) { + // nothing to do here, just double-quote as below + } + return String.format( + "\"%s\"", + object.toString() + .replace("\r", "\\r") + .replace("\n", "\\n") + .replace("\"", "\\\"") + ); + } + + String jsonValue(Object cellObj, String initIndent, String indent) { + if (cellObj instanceof PrettyTable) { + PrettyTable t = (PrettyTable) cellObj; + if (t.getDefaultOutput() == OutputFormat.JSON) { + String out = t.toJson().replace("\n", "\n" + initIndent); + return out.substring(0, out.lastIndexOf("\n" + initIndent)); + } + } + return jsonEncode(cellObj); + } + + String jsonRow(List row, String initIndent, String indent, List headers) { + List rowValues = new ArrayList<>(); + for (int cIdx = 0; cIdx < row.size(); cIdx++) { + String value = this.jsonValue(row.get(cIdx), initIndent + indent, indent); + if (!headers.isEmpty()) { + rowValues.add( + String.format("%s%s\"%s\": %s", + initIndent, + indent, + headers.get(cIdx), + value) + ); + } else { + rowValues.add( + String.format("%s%s%s", initIndent, indent, value) + ); + } + } + return StringUtils.join(rowValues, ",\n"); + } + + @Override + public String render(PrettyTable table, boolean removeRedundant) { + StringBuilder result = new StringBuilder(); + String indent = " "; + result.append("[\n"); + String entryPrefix = ""; + List headers = table.getHeaders(); + if (!headers.isEmpty()) { + result.append(String.format("%s[\n", indent)); + List headerValues = headers.stream() + .map(h -> String.format("%s%s\"%s\"", indent, indent, h)) + .collect(Collectors.toList()); + result.append(StringUtils.join(headerValues, ",\n") + "\n"); + result.append(String.format("%s]", indent)); + entryPrefix = ",\n"; + } + String entryStart = table.getHeaders().isEmpty() ? "[" : "{"; + String entryEnd = table.getHeaders().isEmpty() ? "]" : "}"; + for (List row : table.getRows()) { + result.append(String.format("%s%s%s\n", entryPrefix, indent, entryStart)); + result.append(jsonRow(row, indent, indent, headers) + "\n"); + result.append(String.format("%s%s", indent, entryEnd)); + entryPrefix = ",\n"; + } + result.append("\n]\n"); + return result.toString(); + } +} diff --git a/src/main/java/rickwporter/prettytable/PrettyTable.java b/src/main/java/rickwporter/prettytable/PrettyTable.java index 19dd080..af51937 100644 --- a/src/main/java/rickwporter/prettytable/PrettyTable.java +++ b/src/main/java/rickwporter/prettytable/PrettyTable.java @@ -1,25 +1,13 @@ package rickwporter.prettytable; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVPrinter; -import org.apache.commons.lang3.StringUtils; - -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; -import java.util.stream.Collectors; public final class PrettyTable { - private static final String HTML_TABLE_TAG = "table"; - private static final String HTML_HEADER_TAG = "thead"; - private static final String HTML_BODY_TAG = "tbody"; - private static final String HTML_ROW_TAG = "tr"; - private static final String HTML_CELL_BODY_TAG = "td"; - private static final String HTML_CELL_HEADER_TAG = "th"; private static final Pattern NEWLINE = Pattern.compile("\\R"); public enum HorizontalAlign { @@ -53,6 +41,10 @@ public PrettyTable(String... hdrs) { this.headers.addAll(Arrays.asList(hdrs)); } + public List getHeaders() { + return this.headers; + } + public void addRow(Object... row) { this.rows.add(Arrays.asList(row)); } @@ -68,6 +60,10 @@ public List getRow(int rowIndex) { return this.rows.get(rowIndex); } + public List> getRows() { + return this.rows; + } + public Object getCell(int rowIndex, int columnIndex) { List row = this.getRow(rowIndex); if (row == null || row.size() <= columnIndex) { @@ -76,6 +72,10 @@ public Object getCell(int rowIndex, int columnIndex) { return row.get(columnIndex); } + public HorizontalAlign getDefaultHorizAlign() { + return this.defaultHorizontal; + } + public void setHorizAlign(int column, HorizontalAlign fmt) { for (int cIdx = this.hAligns.size(); cIdx <= column; cIdx++) { this.hAligns.add(this.defaultHorizontal); @@ -88,18 +88,19 @@ public void setHorizAligns(HorizontalAlign... fmts) { this.hAligns.addAll(Arrays.asList(fmts)); } - private HorizontalAlign getHorizAlign(int column) { - if (this.hAligns.size() <= column) { - return this.defaultHorizontal; - } - return this.hAligns.get(column); + public List getHorizAligns() { + return this.hAligns; } public void setOutputFormat(OutputFormat format) { this.defaultOutput = format; } - private int getMaxWidthForColumn(int column) { + public OutputFormat getDefaultOutput() { + return this.defaultOutput; + } + + public int getMaxWidthForColumn(int column) { int maxWidth = column >= this.headers.size() ? 0 : this.headers.get(column).length(); for (List row : this.rows) { String cellText = row.get(column).toString(); @@ -145,12 +146,12 @@ public void sortByIndex(Integer... indices) { this.sortRows(Arrays.asList(indices)); } - private int getMaxColumns() { + public int getMaxColumns() { // get the # of columns from the first row when there are no headers return this.headers.isEmpty() ? this.rows.get(0).size() : this.headers.size(); } - private List getMaxWidths() { + public List getMaxWidths() { List maxWidths = new ArrayList<>(); for (int cIdx = 0; cIdx < this.getMaxColumns(); cIdx++) { maxWidths.add(getMaxWidthForColumn(cIdx)); @@ -158,243 +159,20 @@ private List getMaxWidths() { return maxWidths; } - private String textRow(List row, List maxWidths) { - StringBuilder result = new StringBuilder(); - List> splits = row.stream() - .map(c -> Arrays.asList(NEWLINE.split(c.toString()))) - .collect(Collectors.toList()); - int maxLines = splits.stream().map(c -> c.size()).max(Integer::compare).get(); - for (int idx = 0; idx < maxLines; idx++) { - List line = new ArrayList<>(); - for (List column : splits) { - line.add(idx < column.size() ? column.get(idx) : ""); - } - result.append(this.textRowLine(line, maxWidths)); - } - return result.toString(); - } - - private String textRowLine(List row, List maxWidths) { - StringBuilder result = new StringBuilder(); - result.append("|"); - for (int cIdx = 0; cIdx < row.size(); cIdx++) { - String cValue = row.get(cIdx); - Integer cWidth = maxWidths.get(cIdx); - switch (getHorizAlign(cIdx)) { - case LEFT: - result.append(" " + StringUtils.rightPad(cValue, cWidth) + " "); - break; - case RIGHT: - result.append(" " + StringUtils.leftPad(cValue, cWidth) + " "); - break; - case CENTER: - result.append(StringUtils.center(cValue, cWidth + 2)); - break; - } - result.append("|"); - } - result.append("\n"); - return result.toString(); - } - - private String textRule(List maxWidths) { - StringBuilder result = new StringBuilder(); - result.append("+"); - for (int i = 0; i < this.getMaxColumns(); i++) { - for (int j = 0; j < maxWidths.get(i) + 2; j++) { - result.append("-"); - } - result.append("+"); - } - result.append("\n"); - return result.toString(); - } - String toText(boolean removeRedundant) { - List maxWidths = getMaxWidths(); - StringBuilder result = new StringBuilder(); - if (!this.headers.isEmpty()) { - result.append(textRule(maxWidths)); - result.append(textRow(this.headers, maxWidths)); - } - result.append(textRule(maxWidths)); - List lastRow = new ArrayList<>(); - for (List row : this.rows) { - List currentRow = row.stream().map(c -> c.toString()).collect(Collectors.toList()); - List fullRow = new ArrayList(currentRow); // make a copy before manipulating - if (removeRedundant) { - for (int i = 0; i < lastRow.size(); i++) { - if (!lastRow.get(i).equals(currentRow.get(i))) { - break; - } - currentRow.set(i, ""); - } - lastRow = fullRow; - } - result.append(textRow(currentRow, maxWidths)); - } - result.append(textRule(maxWidths)); - return result.toString(); + return formattedString(OutputFormat.TEXT, removeRedundant); } String toCsv() { - try { - StringBuilder result = new StringBuilder(); - CSVFormat format = CSVFormat.DEFAULT.builder().setRecordSeparator("\n").build(); - CSVPrinter printer = new CSVPrinter(result, format); - if (!this.headers.isEmpty()) { - printer.printRecord(this.headers); - } - for (List row : this.rows) { - List current = row.stream().map(c -> c.toString()).collect(Collectors.toList()); - printer.printRecord(current); - } - printer.close(); - return result.toString(); - } catch (IOException ex) { - // nothing to do here - } - return null; - } - - String htmlColumnFormat(Integer column) { - if (this.hAligns.isEmpty()) { - return ""; - } - return " style=\"" + this.getHorizAlign(column).getHtmlStyle() + "\""; - } - - String htmlRow(List row, String initIndent, String indent, String cellTag) { - String result = String.format("%s<%s>\n", initIndent, HTML_ROW_TAG); - for (int column = 0; column < row.size(); column++) { - Object obj = row.get(column); - String cellFormat = htmlColumnFormat(column); - result += String.format( - "%s%s<%s%s>%s\n", initIndent, indent, cellTag, cellFormat, obj.toString(), cellTag - ); - } - result += String.format("%s\n", initIndent, HTML_ROW_TAG); - return result; + return formattedString(OutputFormat.CSV, false); } String toHtml(boolean removeRedundant) { - StringBuilder result = new StringBuilder(); - String indent = " "; - result.append(String.format("<%s>\n", HTML_TABLE_TAG)); - if (!this.headers.isEmpty()) { - result.append(String.format("%s<%s>\n", indent, HTML_HEADER_TAG)); - result.append(htmlRow(this.headers, indent + indent, indent, HTML_CELL_HEADER_TAG)); - result.append(String.format("%s\n", indent, HTML_HEADER_TAG)); - } - result.append(String.format("%s<%s>\n", indent, HTML_BODY_TAG)); - List lastRow = new ArrayList<>(); - for (List row : this.rows) { - List currentRow = row.stream() - .map(c -> { - if (c instanceof PrettyTable) { - PrettyTable t = (PrettyTable) c; - if (t.defaultOutput == OutputFormat.HTML) { - String out = "\n" + t.toHtml(true); - out = out.replaceAll("\n", "\n" + indent + indent + indent + indent); - return out.substring(0, out.lastIndexOf(indent)); - } - return t.toString().replace("\n", "
"); - } - return c.toString(); - }) - .collect(Collectors.toList()); - List fullRow = new ArrayList(currentRow); // make a copy before manipulating - if (removeRedundant) { - for (int i = 0; i < lastRow.size(); i++) { - if (!lastRow.get(i).equals(currentRow.get(i))) { - break; - } - currentRow.set(i, ""); - } - lastRow = fullRow; - } - result.append(htmlRow(currentRow, indent + indent, indent, HTML_CELL_BODY_TAG)); - } - result.append(String.format("%s\n", indent, HTML_BODY_TAG)); - result.append(String.format("\n", HTML_TABLE_TAG)); - return result.toString(); - } - - String jsonEncode(Object object) { - if (object instanceof Integer) { - return object.toString(); - } - try { - Integer value = Integer.parseInt(object.toString()); - return value.toString(); - } catch (NumberFormatException ex) { - // nothing to do here, just double-quote as below - } - return String.format( - "\"%s\"", - object.toString() - .replace("\r", "\\r") - .replace("\n", "\\n") - .replace("\"", "\\\"") - ); - } - - String jsonValue(Object cellObj, String initIndent, String indent) { - if (cellObj instanceof PrettyTable) { - PrettyTable t = (PrettyTable) cellObj; - if (t.defaultOutput == OutputFormat.JSON) { - String out = t.toJson().replace("\n", "\n" + initIndent); - return out.substring(0, out.lastIndexOf("\n" + initIndent)); - } - } - return jsonEncode(cellObj); - } - - String jsonRow(List row, String initIndent, String indent) { - List rowValues = new ArrayList<>(); - for (int cIdx = 0; cIdx < row.size(); cIdx++) { - String value = this.jsonValue(row.get(cIdx), initIndent + indent, indent); - if (!this.headers.isEmpty()) { - rowValues.add( - String.format("%s%s\"%s\": %s", - initIndent, - indent, - this.headers.get(cIdx), - value) - ); - } else { - rowValues.add( - String.format("%s%s%s", initIndent, indent, value) - ); - } - } - return StringUtils.join(rowValues, ",\n"); + return formattedString(OutputFormat.HTML, removeRedundant); } String toJson() { - StringBuilder result = new StringBuilder(); - String indent = " "; - result.append("[\n"); - String entryPrefix = ""; - if (!this.headers.isEmpty()) { - result.append(String.format("%s[\n", indent)); - List headerValues = this.headers.stream() - .map(h -> String.format("%s%s\"%s\"", indent, indent, h)) - .collect(Collectors.toList()); - result.append(StringUtils.join(headerValues, ",\n") + "\n"); - result.append(String.format("%s]", indent)); - entryPrefix = ",\n"; - } - String entryStart = this.headers.isEmpty() ? "[" : "{"; - String entryEnd = this.headers.isEmpty() ? "]" : "}"; - for (List row : this.rows) { - result.append(String.format("%s%s%s\n", entryPrefix, indent, entryStart)); - result.append(jsonRow(row, indent, indent) + "\n"); - result.append(String.format("%s%s", indent, entryEnd)); - entryPrefix = ",\n"; - } - result.append("\n]\n"); - return result.toString(); + return formattedString(OutputFormat.JSON, false); } public String formattedString(OutputFormat format) { @@ -402,17 +180,26 @@ public String formattedString(OutputFormat format) { } public String formattedString(OutputFormat format, boolean removeRedundant) { + TableRenderInterface renderer = null; switch (format) { case TEXT: - return this.toText(removeRedundant); + renderer = new TextTableRender(); + break; case CSV: - return this.toCsv(); + renderer = new CsvTableRender(); + break; case JSON: - return this.toJson(); + renderer = new JsonTableRender(); + break; case HTML: - return this.toHtml(removeRedundant); + renderer = new HtmlTableRender(); + break; } - return String.format("Unhandled format=%s", format); + return render(renderer, removeRedundant); + } + + public String render(TableRenderInterface renderer, boolean removeRedundant) { + return renderer.render(this, removeRedundant); } public String toString() { diff --git a/src/main/java/rickwporter/prettytable/TableRenderInterface.java b/src/main/java/rickwporter/prettytable/TableRenderInterface.java new file mode 100644 index 0000000..ba8c8f5 --- /dev/null +++ b/src/main/java/rickwporter/prettytable/TableRenderInterface.java @@ -0,0 +1,5 @@ +package rickwporter.prettytable; + +public interface TableRenderInterface { + String render(PrettyTable table, boolean removeRedundant); +} diff --git a/src/main/java/rickwporter/prettytable/TextTableRender.java b/src/main/java/rickwporter/prettytable/TextTableRender.java new file mode 100644 index 0000000..b37e42b --- /dev/null +++ b/src/main/java/rickwporter/prettytable/TextTableRender.java @@ -0,0 +1,100 @@ +package rickwporter.prettytable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + +import rickwporter.prettytable.PrettyTable.HorizontalAlign; + +class TextTableRender implements TableRenderInterface { + private static final Pattern NEWLINE = Pattern.compile("\\R"); + + private String textRow(List row, List maxWidths, List hAligns) { + StringBuilder result = new StringBuilder(); + List> splits = row.stream() + .map(c -> Arrays.asList(NEWLINE.split(c.toString()))) + .collect(Collectors.toList()); + int maxLines = splits.stream().map(c -> c.size()).max(Integer::compare).get(); + for (int idx = 0; idx < maxLines; idx++) { + List line = new ArrayList<>(); + for (List column : splits) { + line.add(idx < column.size() ? column.get(idx) : ""); + } + result.append(this.textRowLine(line, maxWidths, hAligns)); + } + return result.toString(); + } + + private String textRowLine(List row, List maxWidths, List hAligns) { + StringBuilder result = new StringBuilder(); + result.append("|"); + for (int cIdx = 0; cIdx < row.size(); cIdx++) { + String cValue = row.get(cIdx); + Integer cWidth = maxWidths.get(cIdx); + switch (hAligns.get(cIdx)) { + case LEFT: + result.append(" " + StringUtils.rightPad(cValue, cWidth) + " "); + break; + case RIGHT: + result.append(" " + StringUtils.leftPad(cValue, cWidth) + " "); + break; + case CENTER: + result.append(StringUtils.center(cValue, cWidth + 2)); + break; + } + result.append("|"); + } + result.append("\n"); + return result.toString(); + } + + private String textRule(List maxWidths, Integer maxColumns) { + StringBuilder result = new StringBuilder(); + result.append("+"); + for (int i = 0; i < maxColumns; i++) { + for (int j = 0; j < maxWidths.get(i) + 2; j++) { + result.append("-"); + } + result.append("+"); + } + result.append("\n"); + return result.toString(); + } + + @Override + public String render(PrettyTable table, boolean removeRedundant) { + List maxWidths = table.getMaxWidths(); + StringBuilder result = new StringBuilder(); + Integer maxColumns = table.getMaxColumns(); + List hAligns = new ArrayList<>(table.getHorizAligns()); + for (int i = hAligns.size(); i <= maxColumns; i++) { + hAligns.add(table.getDefaultHorizAlign()); + } + + if (!table.getHeaders().isEmpty()) { + result.append(textRule(maxWidths, maxColumns)); + result.append(textRow(table.getHeaders(), maxWidths, hAligns)); + } + result.append(textRule(maxWidths, maxColumns)); + List lastRow = new ArrayList<>(); + for (List row : table.getRows()) { + List currentRow = row.stream().map(c -> c.toString()).collect(Collectors.toList()); + List fullRow = new ArrayList(currentRow); // make a copy before manipulating + if (removeRedundant) { + for (int i = 0; i < lastRow.size(); i++) { + if (!lastRow.get(i).equals(currentRow.get(i))) { + break; + } + currentRow.set(i, ""); + } + lastRow = fullRow; + } + result.append(textRow(currentRow, maxWidths, hAligns)); + } + result.append(textRule(maxWidths, maxColumns)); + return result.toString(); + } +}