Skip to content

Commit c3eb510

Browse files
committed
Rewrite table renderer: proper column-width-aware ASCII box drawing
1 parent 5b6806a commit c3eb510

1 file changed

Lines changed: 89 additions & 12 deletions

File tree

src/main/java/com/jtdev/website/service/ContentService.java

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,12 @@ public String getMarkdownContent(String path) throws IOException {
9090
private String convertHtmlToAscii(String html, String dir) {
9191
// First pass: Convert headers with dynamic borders
9292
html = convertDynamicHeaders(html);
93-
93+
9494
html = formatNestedLists(html);
9595

96+
// Convert tables before the general regex pass
97+
html = convertTablesToAscii(html);
98+
9699
// Convert HTML elements to ASCII formatting
97100
String text = html
98101
// Headers already converted above
@@ -126,17 +129,7 @@ private String convertHtmlToAscii(String html, String dir) {
126129

127130
// Bold and italic
128131
.replaceAll("<strong[^>]*>([^<]*)</strong>", "**$1**")
129-
.replaceAll("<em[^>]*>([^<]*)</em>", "*$1*")
130-
131-
// Tables (simplified ASCII representation)
132-
.replaceAll("<table[^>]*>", "\n┌")
133-
.replaceAll("</table>", "┘\n")
134-
.replaceAll("<tr[^>]*>", "")
135-
.replaceAll("</tr>", "\n├")
136-
.replaceAll("<th[^>]*>", "─ ")
137-
.replaceAll("</th>", " ─")
138-
.replaceAll("<td[^>]*>", "│ ")
139-
.replaceAll("</td>", " │");
132+
.replaceAll("<em[^>]*>([^<]*)</em>", "*$1*");
140133

141134
// Handle images with ASCII art
142135
Pattern imgPattern = Pattern.compile("<img[^>]*src=\"([^\"]*)\"[^>]*>");
@@ -251,6 +244,90 @@ int nextIndex() {
251244
}
252245
}
253246

247+
private String convertTablesToAscii(String html) {
248+
Pattern tablePattern = Pattern.compile("<table[^>]*>(.*?)</table>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
249+
Matcher tableMatcher = tablePattern.matcher(html);
250+
StringBuffer sb = new StringBuffer();
251+
while (tableMatcher.find()) {
252+
String asciiTable = renderTableAsAscii(tableMatcher.group(1));
253+
tableMatcher.appendReplacement(sb, Matcher.quoteReplacement(asciiTable));
254+
}
255+
tableMatcher.appendTail(sb);
256+
return sb.toString();
257+
}
258+
259+
private String renderTableAsAscii(String tableHtml) {
260+
List<List<String>> rows = new ArrayList<>();
261+
262+
Pattern rowPattern = Pattern.compile("<tr[^>]*>(.*?)</tr>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
263+
Matcher rowMatcher = rowPattern.matcher(tableHtml);
264+
while (rowMatcher.find()) {
265+
List<String> cells = new ArrayList<>();
266+
Pattern cellPattern = Pattern.compile("<t[hd][^>]*>(.*?)</t[hd]>", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
267+
Matcher cellMatcher = cellPattern.matcher(rowMatcher.group(1));
268+
while (cellMatcher.find()) {
269+
String cell = cellMatcher.group(1)
270+
.replaceAll("<[^>]+>", "")
271+
.replaceAll("&amp;", "&").replaceAll("&lt;", "<")
272+
.replaceAll("&gt;", ">").replaceAll("&quot;", "\"")
273+
.replaceAll("&#39;", "'").replaceAll("&nbsp;", " ")
274+
.trim();
275+
cells.add(cell);
276+
}
277+
if (!cells.isEmpty()) rows.add(cells);
278+
}
279+
280+
if (rows.isEmpty()) return "";
281+
282+
int numCols = rows.stream().mapToInt(List::size).max().orElse(0);
283+
int[] colWidths = new int[numCols];
284+
for (List<String> row : rows) {
285+
for (int i = 0; i < row.size(); i++) {
286+
colWidths[i] = Math.max(colWidths[i], row.get(i).length());
287+
}
288+
}
289+
290+
StringBuilder ascii = new StringBuilder("\n");
291+
292+
// Top border
293+
ascii.append("┌");
294+
for (int i = 0; i < numCols; i++) {
295+
ascii.append("─".repeat(colWidths[i] + 2));
296+
ascii.append(i < numCols - 1 ? "┬" : "┐");
297+
}
298+
ascii.append("\n");
299+
300+
for (int r = 0; r < rows.size(); r++) {
301+
List<String> row = rows.get(r);
302+
ascii.append("│");
303+
for (int i = 0; i < numCols; i++) {
304+
String cell = i < row.size() ? row.get(i) : "";
305+
ascii.append(" ").append(cell).append(" ".repeat(colWidths[i] - cell.length() + 1)).append("│");
306+
}
307+
ascii.append("\n");
308+
309+
if (r == 0 && rows.size() > 1) {
310+
// Header separator
311+
ascii.append("├");
312+
for (int i = 0; i < numCols; i++) {
313+
ascii.append("─".repeat(colWidths[i] + 2));
314+
ascii.append(i < numCols - 1 ? "┼" : "┤");
315+
}
316+
ascii.append("\n");
317+
} else if (r == rows.size() - 1) {
318+
// Bottom border
319+
ascii.append("└");
320+
for (int i = 0; i < numCols; i++) {
321+
ascii.append("─".repeat(colWidths[i] + 2));
322+
ascii.append(i < numCols - 1 ? "┴" : "┘");
323+
}
324+
ascii.append("\n");
325+
}
326+
}
327+
328+
return ascii.toString();
329+
}
330+
254331
private String convertDynamicHeaders(String html) {
255332
// Convert H1 headers with dynamic borders
256333
Pattern h1Pattern = Pattern.compile("<h1[^>]*>(.*?)</h1>", Pattern.DOTALL);

0 commit comments

Comments
 (0)