some fixes for markdown

This commit is contained in:
Radek Davidek 2026-05-18 13:05:00 +02:00
parent de1ae57da7
commit 4ca854f6c2

View File

@ -831,6 +831,9 @@ public class FileEditor extends JFrame {
styles.addRule("blockquote { margin: 0 0 10px 12px; padding-left: 10px; border-left: 3px solid #808080; }");
styles.addRule("pre { font-family: Monospaced; font-size: " + fontSize + "pt; margin: 0 0 10px 0; padding: 8px; }");
styles.addRule("code { font-family: Monospaced; }");
styles.addRule("table { border-collapse: collapse; margin: 0 0 10px 0; }");
styles.addRule("th, td { border: 1px solid #808080; padding: 4px 8px; }");
styles.addRule("th { font-weight: bold; }");
styles.addRule("a { color: #2f7ed8; }");
return kit;
}
@ -838,15 +841,17 @@ public class FileEditor extends JFrame {
private String markdownToHtml(String markdown) {
StringBuilder body = new StringBuilder();
String[] lines = markdown.replace("\r\n", "\n").replace('\r', '\n').split("\n", -1);
StringBuilder paragraph = new StringBuilder();
java.util.List<String> paragraph = new java.util.ArrayList<>();
StringBuilder list = null;
String listTag = null;
boolean inFence = false;
StringBuilder fence = new StringBuilder();
for (String line : lines) {
for (int lineIndex = 0; lineIndex < lines.length; lineIndex++) {
String line = lines[lineIndex];
String trimmed = line.trim();
if (trimmed.startsWith("```")) {
String fenceLine = normalizeFenceMarker(trimmed);
if (fenceLine.startsWith("```")) {
if (inFence) {
body.append("<pre><code>").append(escapeHtml(fence.toString())).append("</code></pre>");
fence.setLength(0);
@ -855,7 +860,14 @@ public class FileEditor extends JFrame {
flushParagraph(body, paragraph);
list = flushList(body, list, listTag);
listTag = null;
inFence = true;
String openerRest = fenceLine.substring(3).trim();
int sameLineClose = findFenceClose(openerRest);
if (sameLineClose >= 0) {
String code = stripFenceLanguage(openerRest.substring(0, sameLineClose).trim());
body.append("<pre><code>").append(escapeHtml(code)).append("</code></pre>");
} else {
inFence = true;
}
}
continue;
}
@ -888,6 +900,14 @@ public class FileEditor extends JFrame {
continue;
}
if (isTableStart(lines, lineIndex)) {
flushParagraph(body, paragraph);
list = flushList(body, list, listTag);
listTag = null;
lineIndex = appendMarkdownTable(body, lines, lineIndex);
continue;
}
Matcher unordered = Pattern.compile("^[-*+]\\s+(.+)$").matcher(trimmed);
Matcher ordered = Pattern.compile("^\\d+[.)]\\s+(.+)$").matcher(trimmed);
if (unordered.matches() || ordered.matches()) {
@ -914,10 +934,11 @@ public class FileEditor extends JFrame {
continue;
}
if (paragraph.length() > 0) {
paragraph.append(' ');
if (list != null) {
list = flushList(body, list, listTag);
listTag = null;
}
paragraph.append(trimmed);
paragraph.add(trimmed);
}
if (inFence) {
@ -928,10 +949,112 @@ public class FileEditor extends JFrame {
return "<html><body>" + body + "</body></html>";
}
private void flushParagraph(StringBuilder body, StringBuilder paragraph) {
if (paragraph.length() == 0) return;
body.append("<p>").append(renderInline(paragraph.toString())).append("</p>");
paragraph.setLength(0);
private String normalizeFenceMarker(String text) {
return text.replace("\\`", "`");
}
private int findFenceClose(String text) {
int plain = text.indexOf("```");
int escaped = text.indexOf("\\`\\`\\`");
if (plain < 0) return escaped;
if (escaped < 0) return plain;
return Math.min(plain, escaped);
}
private String stripFenceLanguage(String text) {
int spaceIndex = text.indexOf(' ');
if (spaceIndex <= 0) return text;
String firstWord = text.substring(0, spaceIndex);
if (firstWord.matches("[A-Za-z][A-Za-z0-9_+.-]*")) {
return text.substring(spaceIndex + 1).trim();
}
return text;
}
private boolean isTableStart(String[] lines, int lineIndex) {
if (lineIndex + 1 >= lines.length) return false;
java.util.List<String> header = splitMarkdownTableRow(lines[lineIndex].trim());
java.util.List<String> separator = splitMarkdownTableRow(lines[lineIndex + 1].trim());
if (header.size() < 2 || separator.size() != header.size()) return false;
for (String cell : separator) {
if (!cell.trim().matches(":?-{3,}:?")) {
return false;
}
}
return true;
}
private int appendMarkdownTable(StringBuilder body, String[] lines, int startIndex) {
java.util.List<String> header = splitMarkdownTableRow(lines[startIndex].trim());
body.append("<table border=\"1\" cellspacing=\"0\" cellpadding=\"4\"><tr>");
for (String cell : header) {
body.append("<th>").append(renderInline(cell.trim())).append("</th>");
}
body.append("</tr>");
int lineIndex = startIndex + 2;
while (lineIndex < lines.length) {
String trimmed = lines[lineIndex].trim();
if (trimmed.isEmpty()) break;
java.util.List<String> cells = splitMarkdownTableRow(trimmed);
if (cells.size() != header.size()) break;
body.append("<tr>");
for (String cell : cells) {
body.append("<td>").append(renderInline(cell.trim())).append("</td>");
}
body.append("</tr>");
lineIndex++;
}
body.append("</table>");
return lineIndex - 1;
}
private java.util.List<String> splitMarkdownTableRow(String row) {
java.util.List<String> cells = new java.util.ArrayList<>();
if (!row.contains("|")) return cells;
String content = row;
if (content.startsWith("|")) {
content = content.substring(1);
}
if (content.endsWith("|")) {
content = content.substring(0, content.length() - 1);
}
StringBuilder cell = new StringBuilder();
boolean escaped = false;
for (int i = 0; i < content.length(); i++) {
char c = content.charAt(i);
if (escaped) {
cell.append(c);
escaped = false;
} else if (c == '\\') {
escaped = true;
} else if (c == '|') {
cells.add(cell.toString());
cell.setLength(0);
} else {
cell.append(c);
}
}
if (escaped) {
cell.append('\\');
}
cells.add(cell.toString());
return cells;
}
private void flushParagraph(StringBuilder body, java.util.List<String> paragraph) {
if (paragraph.isEmpty()) return;
body.append("<p>");
for (int i = 0; i < paragraph.size(); i++) {
if (i > 0) {
body.append("<br>");
}
body.append(renderInline(paragraph.get(i)));
}
body.append("</p>");
paragraph.clear();
}
private StringBuilder flushList(StringBuilder body, StringBuilder list, String listTag) {
@ -952,25 +1075,60 @@ public class FileEditor extends JFrame {
Matcher codeMatcher = Pattern.compile("`([^`]+)`").matcher(text);
StringBuffer protectedText = new StringBuffer();
while (codeMatcher.find()) {
String token = "{{KF_MD_CODE_" + codeSpans.size() + "}}";
String token = "%%KFCODE" + codeSpans.size() + "%%";
codeSpans.add("<code>" + escapeHtml(codeMatcher.group(1)) + "</code>");
codeMatcher.appendReplacement(protectedText, Matcher.quoteReplacement(token));
}
codeMatcher.appendTail(protectedText);
String html = escapeHtml(protectedText.toString());
java.util.List<String> escapedChars = new java.util.ArrayList<>();
String escapesProtected = protectMarkdownEscapes(protectedText.toString(), escapedChars);
String html = escapeHtml(escapesProtected);
html = html.replaceAll("\\*\\*([^*]+)\\*\\*", "<strong>$1</strong>");
html = html.replaceAll("__([^_]+)__", "<strong>$1</strong>");
html = html.replaceAll("(?<!\\*)\\*([^*]+)\\*(?!\\*)", "<em>$1</em>");
html = html.replaceAll("(?<!_)_([^_]+)_(?!_)", "<em>$1</em>");
html = replaceLinks(html);
for (int i = 0; i < escapedChars.size(); i++) {
html = html.replace("%%KFESC" + i + "%%", escapedChars.get(i));
}
for (int i = 0; i < codeSpans.size(); i++) {
html = html.replace("{{KF_MD_CODE_" + i + "}}", codeSpans.get(i));
html = html.replace("%%KFCODE" + i + "%%", codeSpans.get(i));
}
return html;
}
private String protectMarkdownEscapes(String text, java.util.List<String> escapedChars) {
StringBuilder sb = new StringBuilder(text.length());
boolean escaped = false;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (escaped) {
if (isMarkdownEscapable(c)) {
String token = "%%KFESC" + escapedChars.size() + "%%";
escapedChars.add(escapeHtml(String.valueOf(c)));
sb.append(token);
} else {
sb.append('\\').append(c);
}
escaped = false;
} else if (c == '\\') {
escaped = true;
} else {
sb.append(c);
}
}
if (escaped) {
sb.append('\\');
}
return sb.toString();
}
private boolean isMarkdownEscapable(char c) {
return "\\`*_{}[]<>()#+-.!|/:".indexOf(c) >= 0;
}
private String replaceLinks(String html) {
Matcher m = Pattern.compile("\\[([^\\]]+)]\\(([^\\s)]+)\\)").matcher(html);
StringBuffer sb = new StringBuffer();