Skip to content

Commit dbd9257

Browse files
authored
Keep {} in Markdown rendering (#11196)
* fix: removed '{' and '}' swallow when not in command in HTMLChars and adapted tests * doc: updated CHANGELOG.md * fix: added old test cases and adapted them to apply requested changes * fix: fixed small rule in HTMLChars formatter to fix faulty tests * test: added test case in MardownFormatterTest for string with braces * test: added more test cases in MarkdownFormatterTest * fix: fixed checkstyle erros in HTMLChars * test: added test about code between quotes in MardownFormatterTest * feat: added parameter to keep braces to HTMLChars * feat: modified default preview to keep braces when formatting markdown * test: added parameterized test cases to MardownFormatterTest, updated preferences migrations and made keep braces mode name more clear in HTMLChars * fix: fixed issue about preferences migrations * fix: rolling back modif. in HTMLCharsTest and enabled case-insensitive equality for braces keep mode in HTMLChars * test: added test for markdown lists in MarkdowFormatterTest
1 parent 0f9e0e7 commit dbd9257

File tree

7 files changed

+123
-27
lines changed

7 files changed

+123
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
2727
### Fixed
2828

2929
- We fixed an issue where entry type with duplicate fields prevented opening existing libraries with custom entry types [#11127](https://github.com/JabRef/jabref/issues/11127)
30+
- We fixed an issue where Markdown rendering removed braces from the text. [#10928](https://github.com/JabRef/jabref/issues/10928)
3031
- We fixed an issue when the file was flagged as changed on disk in the case of content selectors or groups. [#9064](https://github.com/JabRef/jabref/issues/9064)
3132
- We fixed crash on opening the entry editor when auto completion is enabled. [#11188](https://github.com/JabRef/jabref/issues/11188)
3233
- We fixed the usage of the key binding for "Clear search" (default: <kbd>Escape</kbd>). [#10764](https://github.com/JabRef/jabref/issues/10764)

src/main/java/org/jabref/logic/layout/format/HTMLChars.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import java.util.Objects;
55
import java.util.regex.Pattern;
66

7-
import org.jabref.logic.layout.LayoutFormatter;
7+
import org.jabref.logic.layout.ParamLayoutFormatter;
88
import org.jabref.logic.util.strings.HTMLUnicodeConversionMaps;
99
import org.jabref.model.strings.StringUtil;
1010

1111
/**
12-
* This formatter escapes characters so they are suitable for HTML.
12+
* This formatter escapes characters so that they are suitable for HTML.
1313
*/
14-
public class HTMLChars implements LayoutFormatter {
14+
public class HTMLChars implements ParamLayoutFormatter {
1515

1616
private static final Map<String, String> HTML_CHARS = HTMLUnicodeConversionMaps.LATEX_HTML_CONVERSION_MAP;
1717
/**
@@ -23,6 +23,15 @@ public class HTMLChars implements LayoutFormatter {
2323
* */
2424
private static final Pattern HTML_ENTITY_PATTERN = Pattern.compile("&(?!(?:[a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6});)");
2525

26+
private boolean keepCurlyBraces = false;
27+
28+
@Override
29+
public void setArgument(String arg) {
30+
if ("keepCurlyBraces".equalsIgnoreCase(arg)) {
31+
this.keepCurlyBraces = true;
32+
}
33+
}
34+
2635
@Override
2736
public String format(String inField) {
2837
String field = normalizedField(inField);
@@ -49,7 +58,7 @@ public String format(String inField) {
4958
escaped = true;
5059
incommand = true;
5160
currentCommand = new StringBuilder();
52-
} else if (!incommand && ((c == '{') || (c == '}'))) {
61+
} else if (!this.keepCurlyBraces && !incommand && ((c == '{') || (c == '}'))) {
5362
// Swallow the brace.
5463
} else if (Character.isLetter(c) || StringUtil.SPECIAL_COMMAND_CHARS.contains(String.valueOf(c))) {
5564
escaped = false;
@@ -73,7 +82,7 @@ public String format(String inField) {
7382
String commandBody;
7483
if (c == '{') {
7584
String part = StringUtil.getPart(field, i, false);
76-
i += part.length();
85+
i += this.keepCurlyBraces ? part.length() + 1 : part.length();
7786
commandBody = part;
7887
} else {
7988
commandBody = field.substring(i, i + 1);
@@ -83,7 +92,6 @@ public String format(String inField) {
8392
sb.append(Objects.requireNonNullElse(result, commandBody));
8493

8594
incommand = false;
86-
escaped = false;
8795
} else {
8896
// Are we already at the end of the string?
8997
if ((i + 1) == field.length()) {
@@ -109,11 +117,14 @@ public String format(String inField) {
109117
String tag = getHTMLTag(command);
110118
if (!tag.isEmpty()) {
111119
String part = StringUtil.getPart(field, i, true);
120+
if (this.keepCurlyBraces && (c == '{' || (c == '}'))) {
121+
i++;
122+
}
112123
i += part.length();
113124
sb.append('<').append(tag).append('>').append(part).append("</").append(tag).append('>');
114125
} else if (c == '{') {
115126
String argument = StringUtil.getPart(field, i, true);
116-
i += argument.length();
127+
i += this.keepCurlyBraces ? argument.length() + 1 : argument.length();
117128
// handle common case of general latex command
118129
String result = HTML_CHARS.get(command + argument);
119130
// If found, then use translated version. If not, then keep
@@ -133,10 +144,14 @@ public String format(String inField) {
133144
} else if (c == '}') {
134145
// This end brace terminates a command. This can be the case in
135146
// constructs like {\aa}. The correct behaviour should be to
136-
// substitute the evaluated command and swallow the brace:
147+
// substitute the evaluated command.
137148
String result = HTML_CHARS.get(command);
138149
// If the command is unknown, just print it:
139150
sb.append(Objects.requireNonNullElse(result, command));
151+
// We only keep the brace if we are in 'KEEP' mode.
152+
if (this.keepCurlyBraces) {
153+
sb.append(c);
154+
}
140155
} else {
141156
String result = HTML_CHARS.get(command);
142157
sb.append(Objects.requireNonNullElse(result, command));
@@ -170,7 +185,7 @@ private String normalizedField(String inField) {
170185
.replaceAll("[\\n]{2,}", "<p>") // Replace double line breaks with <p>
171186
.replace("\n", "<br>") // Replace single line breaks with <br>
172187
.replace("\\$", "&dollar;") // Replace \$ with &dollar;
173-
.replaceAll("\\$([^$]*)\\$", "\\{$1\\}");
188+
.replaceAll("\\$([^$]*)\\$", this.keepCurlyBraces ? "\\\\{$1\\\\}" : "$1}");
174189
}
175190

176191
private String getHTMLTag(String latexCommand) {

src/main/java/org/jabref/migrations/PreferencesMigrations.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ protected static void upgradePreviewStyle(JabRefPreferences prefs) {
331331
String currentPreviewStyle = prefs.get(JabRefPreferences.PREVIEW_STYLE);
332332
String migratedStyle = currentPreviewStyle.replace("\\begin{review}<BR><BR><b>Review: </b> \\format[HTMLChars]{\\review} \\end{review}", "\\begin{comment}<BR><BR><b>Comment: </b> \\format[Markdown,HTMLChars]{\\comment} \\end{comment}")
333333
.replace("\\format[HTMLChars]{\\comment}", "\\format[Markdown,HTMLChars]{\\comment}")
334+
.replace("\\format[Markdown,HTMLChars]{\\comment}", "\\format[Markdown,HTMLChars(keepCurlyBraces)]{\\comment}")
334335
.replace("<b><i>\\bibtextype</i><a name=\"\\bibtexkey\">\\begin{bibtexkey} (\\bibtexkey)</a>", "<b><i>\\bibtextype</i><a name=\"\\citationkey\">\\begin{citationkey} (\\citationkey)</a>")
335336
.replace("\\end{bibtexkey}</b><br>__NEWLINE__", "\\end{citationkey}</b><br>__NEWLINE__");
336337
prefs.put(JabRefPreferences.PREVIEW_STYLE, migratedStyle);

src/main/java/org/jabref/preferences/JabRefPreferences.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,7 @@ private JabRefPreferences() {
821821
"\\begin{pages}<BR> p. \\format[FormatPagesForHTML]{\\pages}\\end{pages}__NEWLINE__" +
822822
"\\begin{abstract}<BR><BR><b>Abstract: </b>\\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__" +
823823
"\\begin{owncitation}<BR><BR><b>Own citation: </b>\\format[HTMLChars]{\\owncitation} \\end{owncitation}__NEWLINE__" +
824-
"\\begin{comment}<BR><BR><b>Comment: </b>\\format[Markdown,HTMLChars]{\\comment}\\end{comment}__NEWLINE__" +
824+
"\\begin{comment}<BR><BR><b>Comment: </b>\\format[Markdown,HTMLChars(keepCurlyBraces)]{\\comment}\\end{comment}__NEWLINE__" +
825825
"</font>__NEWLINE__");
826826

827827
// set default theme

src/test/java/org/jabref/logic/layout/format/HTMLCharsTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.stream.Stream;
44

55
import org.jabref.logic.layout.LayoutFormatter;
6+
import org.jabref.logic.layout.ParamLayoutFormatter;
67

78
import org.junit.jupiter.api.BeforeEach;
89
import org.junit.jupiter.api.Test;
@@ -15,10 +16,13 @@
1516
public class HTMLCharsTest {
1617

1718
private LayoutFormatter layout;
19+
private ParamLayoutFormatter layoutKeep;
1820

1921
@BeforeEach
2022
public void setUp() {
2123
layout = new HTMLChars();
24+
layoutKeep = new HTMLChars();
25+
layoutKeep.setArgument("keepCurlyBraces");
2226
}
2327

2428
private static Stream<Arguments> provideBasicFormattingData() {
@@ -37,6 +41,7 @@ private static Stream<Arguments> provideBasicFormattingData() {
3741
Arguments.of("&auml;", "{\\\"a}"),
3842
Arguments.of("&auml;", "\\\"a"),
3943
Arguments.of("&Ccedil;", "{\\c{C}}"),
44+
Arguments.of("&Ccedil;", "\\c{C}"),
4045
Arguments.of("&Oogon;&imath;", "\\k{O}\\i"),
4146
Arguments.of("&ntilde; &ntilde; &iacute; &imath; &imath;", "\\~{n} \\~n \\'i \\i \\i")
4247
);
@@ -144,4 +149,23 @@ private static Stream<Arguments> provideHTMLEntityFormattingData() {
144149
void hTMLEntityFormatting(String expected, String input) {
145150
assertEquals(expected, layout.format(input));
146151
}
152+
153+
private static Stream<Arguments> provideBracesKeepFormattingData() {
154+
return Stream.of(
155+
Arguments.of("{&auml;}", "{\\\"{a}}"),
156+
Arguments.of("{&auml;}", "{\\\"a}"),
157+
Arguments.of("{&Ccedil;}", "{\\c{C}}"),
158+
Arguments.of("{<em>hallo</em>}", "{\\emph hallo}"),
159+
Arguments.of("{<b>hallo</b>}", "{\\textbf hallo}"),
160+
Arguments.of("{<b>hallo</b>}", "{\\bf hallo}"),
161+
Arguments.of("{&#39;}", "{\\textquotesingle}"),
162+
Arguments.of("{{ test }}", "{{ test }}")
163+
);
164+
}
165+
166+
@ParameterizedTest
167+
@MethodSource("provideBracesKeepFormattingData")
168+
void bracesKeepFormatting(String expected, String input) {
169+
assertEquals(expected, layoutKeep.format(input));
170+
}
147171
}
Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package org.jabref.logic.layout.format;
22

3+
import java.util.stream.Stream;
4+
35
import org.junit.jupiter.api.BeforeEach;
46
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.params.ParameterizedTest;
8+
import org.junit.jupiter.params.provider.Arguments;
9+
import org.junit.jupiter.params.provider.MethodSource;
510

611
import static org.junit.jupiter.api.Assertions.assertEquals;
7-
import static org.junit.jupiter.api.Assertions.assertFalse;
812
import static org.junit.jupiter.api.Assertions.assertThrows;
913

1014
class MarkdownFormatterTest {
@@ -17,23 +21,74 @@ void setUp() {
1721
}
1822

1923
@Test
20-
void formatWhenFormattingPlainTextThenReturnsTextWrappedInParagraph() {
21-
assertEquals("<p>Hello World</p>", markdownFormatter.format("Hello World"));
22-
}
23-
24-
@Test
25-
void formatWhenFormattingComplexMarkupThenReturnsOnlyOneLine() {
26-
assertFalse(markdownFormatter.format("Markup\n\n* list item one\n* list item 2\n\n rest").contains("\n"));
24+
void formatWhenFormattingNullThenThrowsException() {
25+
Exception exception = assertThrows(NullPointerException.class, () -> markdownFormatter.format(null));
26+
assertEquals("Field Text should not be null, when handed to formatter", exception.getMessage());
2727
}
2828

29-
@Test
30-
void formatWhenFormattingEmptyStringThenReturnsEmptyString() {
31-
assertEquals("", markdownFormatter.format(""));
29+
private static Stream<Arguments> provideMarkdownAndHtml() {
30+
return Stream.of(
31+
Arguments.of("Hello World", "<p>Hello World</p>"),
32+
Arguments.of("""
33+
Markup
34+
35+
* list item one
36+
* list item two
37+
38+
rest
39+
""",
40+
"<p>Markup</p> <ul> <li>list item one</li> <li>list item two</li> </ul> <p>rest</p>"
41+
),
42+
Arguments.of("""
43+
```
44+
Hello World
45+
```
46+
""",
47+
"<pre><code>Hello World </code></pre>"
48+
),
49+
Arguments.of("""
50+
First line
51+
52+
Second line
53+
54+
```java
55+
String test;
56+
```
57+
""",
58+
"<p>First line</p> <p>Second line</p> <pre><code class=\"language-java\">String test; </code></pre>"
59+
),
60+
Arguments.of("""
61+
Some text.
62+
```javascript
63+
let test = "Hello World";
64+
```
65+
66+
```java
67+
String test = "Hello World";
68+
```
69+
Some more text.
70+
""",
71+
"<p>Some text.</p> <pre><code class=\"language-javascript\">let test = &quot;Hello World&quot;; " +
72+
"</code></pre> <pre><code class=\"language-java\">String test = &quot;Hello World&quot;; " +
73+
"</code></pre> <p>Some more text.</p>"
74+
),
75+
Arguments.of("""
76+
Some text.
77+
78+
```java
79+
int foo = 0;
80+
foo = 1;
81+
82+
```
83+
""",
84+
"<p>Some text.</p> <pre><code class=\"language-java\">int foo = 0; foo = 1; </code></pre>"
85+
)
86+
);
3287
}
3388

34-
@Test
35-
void formatWhenFormattingNullThenThrowsException() {
36-
Exception exception = assertThrows(NullPointerException.class, () -> markdownFormatter.format(null));
37-
assertEquals("Field Text should not be null, when handed to formatter", exception.getMessage());
89+
@ParameterizedTest
90+
@MethodSource("provideMarkdownAndHtml")
91+
void formatWhenFormattingCodeBlockThenReturnsCodeBlockInHtml(String markdown, String expectedHtml) {
92+
assertEquals(expectedHtml, markdownFormatter.format(markdown));
3893
}
3994
}

src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ void previewStyleReviewToComment() {
8484

8585
String newPreviewStyle = "<font face=\"sans-serif\">__NEWLINE__"
8686
+ "Customized preview style using reviews and comments:__NEWLINE__"
87-
+ "\\begin{comment}<BR><BR><b>Comment: </b> \\format[Markdown,HTMLChars]{\\comment} \\end{comment}__NEWLINE__"
88-
+ "\\begin{comment} Something: \\format[Markdown,HTMLChars]{\\comment} special \\end{comment}__NEWLINE__"
87+
+ "\\begin{comment}<BR><BR><b>Comment: </b> \\format[Markdown,HTMLChars(keepCurlyBraces)]{\\comment} \\end{comment}__NEWLINE__"
88+
+ "\\begin{comment} Something: \\format[Markdown,HTMLChars(keepCurlyBraces)]{\\comment} special \\end{comment}__NEWLINE__"
8989
+ "</font>__NEWLINE__";
9090

9191
when(prefs.get(JabRefPreferences.PREVIEW_STYLE)).thenReturn(oldPreviewStyle);

0 commit comments

Comments
 (0)