some fixes for markdown
This commit is contained in:
parent
de1ae57da7
commit
4ca854f6c2
@ -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("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("pre { font-family: Monospaced; font-size: " + fontSize + "pt; margin: 0 0 10px 0; padding: 8px; }");
|
||||||
styles.addRule("code { font-family: Monospaced; }");
|
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; }");
|
styles.addRule("a { color: #2f7ed8; }");
|
||||||
return kit;
|
return kit;
|
||||||
}
|
}
|
||||||
@ -838,15 +841,17 @@ public class FileEditor extends JFrame {
|
|||||||
private String markdownToHtml(String markdown) {
|
private String markdownToHtml(String markdown) {
|
||||||
StringBuilder body = new StringBuilder();
|
StringBuilder body = new StringBuilder();
|
||||||
String[] lines = markdown.replace("\r\n", "\n").replace('\r', '\n').split("\n", -1);
|
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;
|
StringBuilder list = null;
|
||||||
String listTag = null;
|
String listTag = null;
|
||||||
boolean inFence = false;
|
boolean inFence = false;
|
||||||
StringBuilder fence = new StringBuilder();
|
StringBuilder fence = new StringBuilder();
|
||||||
|
|
||||||
for (String line : lines) {
|
for (int lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
||||||
|
String line = lines[lineIndex];
|
||||||
String trimmed = line.trim();
|
String trimmed = line.trim();
|
||||||
if (trimmed.startsWith("```")) {
|
String fenceLine = normalizeFenceMarker(trimmed);
|
||||||
|
if (fenceLine.startsWith("```")) {
|
||||||
if (inFence) {
|
if (inFence) {
|
||||||
body.append("<pre><code>").append(escapeHtml(fence.toString())).append("</code></pre>");
|
body.append("<pre><code>").append(escapeHtml(fence.toString())).append("</code></pre>");
|
||||||
fence.setLength(0);
|
fence.setLength(0);
|
||||||
@ -855,7 +860,14 @@ public class FileEditor extends JFrame {
|
|||||||
flushParagraph(body, paragraph);
|
flushParagraph(body, paragraph);
|
||||||
list = flushList(body, list, listTag);
|
list = flushList(body, list, listTag);
|
||||||
listTag = null;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
@ -888,6 +900,14 @@ public class FileEditor extends JFrame {
|
|||||||
continue;
|
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 unordered = Pattern.compile("^[-*+]\\s+(.+)$").matcher(trimmed);
|
||||||
Matcher ordered = Pattern.compile("^\\d+[.)]\\s+(.+)$").matcher(trimmed);
|
Matcher ordered = Pattern.compile("^\\d+[.)]\\s+(.+)$").matcher(trimmed);
|
||||||
if (unordered.matches() || ordered.matches()) {
|
if (unordered.matches() || ordered.matches()) {
|
||||||
@ -914,10 +934,11 @@ public class FileEditor extends JFrame {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paragraph.length() > 0) {
|
if (list != null) {
|
||||||
paragraph.append(' ');
|
list = flushList(body, list, listTag);
|
||||||
|
listTag = null;
|
||||||
}
|
}
|
||||||
paragraph.append(trimmed);
|
paragraph.add(trimmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inFence) {
|
if (inFence) {
|
||||||
@ -928,10 +949,112 @@ public class FileEditor extends JFrame {
|
|||||||
return "<html><body>" + body + "</body></html>";
|
return "<html><body>" + body + "</body></html>";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void flushParagraph(StringBuilder body, StringBuilder paragraph) {
|
private String normalizeFenceMarker(String text) {
|
||||||
if (paragraph.length() == 0) return;
|
return text.replace("\\`", "`");
|
||||||
body.append("<p>").append(renderInline(paragraph.toString())).append("</p>");
|
}
|
||||||
paragraph.setLength(0);
|
|
||||||
|
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) {
|
private StringBuilder flushList(StringBuilder body, StringBuilder list, String listTag) {
|
||||||
@ -952,25 +1075,60 @@ public class FileEditor extends JFrame {
|
|||||||
Matcher codeMatcher = Pattern.compile("`([^`]+)`").matcher(text);
|
Matcher codeMatcher = Pattern.compile("`([^`]+)`").matcher(text);
|
||||||
StringBuffer protectedText = new StringBuffer();
|
StringBuffer protectedText = new StringBuffer();
|
||||||
while (codeMatcher.find()) {
|
while (codeMatcher.find()) {
|
||||||
String token = "{{KF_MD_CODE_" + codeSpans.size() + "}}";
|
String token = "%%KFCODE" + codeSpans.size() + "%%";
|
||||||
codeSpans.add("<code>" + escapeHtml(codeMatcher.group(1)) + "</code>");
|
codeSpans.add("<code>" + escapeHtml(codeMatcher.group(1)) + "</code>");
|
||||||
codeMatcher.appendReplacement(protectedText, Matcher.quoteReplacement(token));
|
codeMatcher.appendReplacement(protectedText, Matcher.quoteReplacement(token));
|
||||||
}
|
}
|
||||||
codeMatcher.appendTail(protectedText);
|
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("__([^_]+)__", "<strong>$1</strong>");
|
html = html.replaceAll("__([^_]+)__", "<strong>$1</strong>");
|
||||||
html = html.replaceAll("(?<!\\*)\\*([^*]+)\\*(?!\\*)", "<em>$1</em>");
|
html = html.replaceAll("(?<!\\*)\\*([^*]+)\\*(?!\\*)", "<em>$1</em>");
|
||||||
html = html.replaceAll("(?<!_)_([^_]+)_(?!_)", "<em>$1</em>");
|
html = html.replaceAll("(?<!_)_([^_]+)_(?!_)", "<em>$1</em>");
|
||||||
html = replaceLinks(html);
|
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++) {
|
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;
|
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) {
|
private String replaceLinks(String html) {
|
||||||
Matcher m = Pattern.compile("\\[([^\\]]+)]\\(([^\\s)]+)\\)").matcher(html);
|
Matcher m = Pattern.compile("\\[([^\\]]+)]\\(([^\\s)]+)\\)").matcher(html);
|
||||||
StringBuffer sb = new StringBuffer();
|
StringBuffer sb = new StringBuffer();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user