Skip to content

Commit ae7a5cf

Browse files
authored
Merge pull request #32843 from ppalaga/230423-javadoc2asciidoc
Improve JavaDoc -> AsciiDoc transformation for lists, paragraphs and code blocks
2 parents 7e8156e + 1616f28 commit ae7a5cf

File tree

5 files changed

+258
-41
lines changed

5 files changed

+258
-41
lines changed

core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/JavaDocParser.java

Lines changed: 173 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ final class JavaDocParser {
5252
private static final String ORDERED_LIST_NODE = "ol";
5353
private static final String SUPER_SCRIPT_NODE = "sup";
5454
private static final String UN_ORDERED_LIST_NODE = "ul";
55+
private static final String PREFORMATED_NODE = "pre";
56+
private static final String BLOCKQUOTE_NODE = "blockquote";
5557

5658
private static final String BIG_ASCIDOC_STYLE = "[.big]";
5759
private static final String LINK_ATTRIBUTE_FORMAT = "[%s]";
@@ -62,6 +64,10 @@ final class JavaDocParser {
6264
private static final String UNORDERED_LIST_ITEM_ASCIDOC_STYLE = " - ";
6365
private static final String UNDERLINE_ASCIDOC_STYLE = "[.underline]";
6466
private static final String LINE_THROUGH_ASCIDOC_STYLE = "[.line-through]";
67+
private static final String HARD_LINE_BREAK_ASCIDOC_STYLE = " +\n";
68+
private static final String CODE_BLOCK_ASCIDOC_STYLE = "```";
69+
private static final String BLOCKQUOTE_BLOCK_ASCIDOC_STYLE = "[quote]\n____";
70+
private static final String BLOCKQUOTE_BLOCK_ASCIDOC_STYLE_END = "____";
6571

6672
private final boolean inlineMacroMode;
6773

@@ -185,25 +191,51 @@ private String htmlJavadocToAsciidoc(JavadocDescription javadocDescription) {
185191
}
186192
}
187193

188-
return sb.toString().trim();
194+
return trim(sb);
189195
}
190196

191197
private void appendHtml(StringBuilder sb, Node node) {
192198
for (Node childNode : node.childNodes()) {
193199
switch (childNode.nodeName()) {
194200
case PARAGRAPH_NODE:
195-
sb.append(NEW_LINE);
201+
newLine(sb);
202+
newLine(sb);
196203
appendHtml(sb, childNode);
197204
break;
205+
case PREFORMATED_NODE:
206+
newLine(sb);
207+
newLine(sb);
208+
sb.append(CODE_BLOCK_ASCIDOC_STYLE);
209+
newLine(sb);
210+
for (Node grandChildNode : childNode.childNodes()) {
211+
unescapeHtmlEntities(sb, grandChildNode.toString());
212+
}
213+
newLineIfNeeded(sb);
214+
sb.append(CODE_BLOCK_ASCIDOC_STYLE);
215+
newLine(sb);
216+
newLine(sb);
217+
break;
218+
case BLOCKQUOTE_NODE:
219+
newLine(sb);
220+
newLine(sb);
221+
sb.append(BLOCKQUOTE_BLOCK_ASCIDOC_STYLE);
222+
newLine(sb);
223+
appendHtml(sb, childNode);
224+
newLineIfNeeded(sb);
225+
sb.append(BLOCKQUOTE_BLOCK_ASCIDOC_STYLE_END);
226+
newLine(sb);
227+
newLine(sb);
228+
break;
198229
case ORDERED_LIST_NODE:
199230
case UN_ORDERED_LIST_NODE:
231+
newLine(sb);
200232
appendHtml(sb, childNode);
201233
break;
202234
case LIST_ITEM_NODE:
203235
final String marker = childNode.parent().nodeName().equals(ORDERED_LIST_NODE)
204236
? ORDERED_LIST_ITEM_ASCIDOC_STYLE
205237
: UNORDERED_LIST_ITEM_ASCIDOC_STYLE;
206-
sb.append(NEW_LINE);
238+
newLine(sb);
207239
sb.append(marker);
208240
appendHtml(sb, childNode);
209241
break;
@@ -213,7 +245,7 @@ private void appendHtml(StringBuilder sb, Node node) {
213245
sb.append(link);
214246
final StringBuilder caption = new StringBuilder();
215247
appendHtml(caption, childNode);
216-
sb.append(String.format(LINK_ATTRIBUTE_FORMAT, caption.toString().trim()));
248+
sb.append(String.format(LINK_ATTRIBUTE_FORMAT, trim(caption)));
217249
break;
218250
case CODE_NODE:
219251
sb.append(BACKTICK);
@@ -269,7 +301,7 @@ private void appendHtml(StringBuilder sb, Node node) {
269301
sb.append(HASH);
270302
break;
271303
case NEW_LINE_NODE:
272-
sb.append(NEW_LINE);
304+
sb.append(HARD_LINE_BREAK_ASCIDOC_STYLE);
273305
break;
274306
case TEXT_NODE:
275307
String text = ((TextNode) childNode).text();
@@ -295,6 +327,142 @@ private void appendHtml(StringBuilder sb, Node node) {
295327
}
296328
}
297329

330+
/**
331+
* Trim the content of the given {@link StringBuilder} holding also AsciiDoc had line break {@code " +\n"}
332+
* for whitespace in addition to characters <= {@code ' '}.
333+
*
334+
* @param sb the {@link StringBuilder} to trim
335+
* @return the trimmed content of the given {@link StringBuilder}
336+
*/
337+
static String trim(StringBuilder sb) {
338+
int length = sb.length();
339+
int offset = 0;
340+
while (offset < length) {
341+
final char ch = sb.charAt(offset);
342+
if (ch == ' '
343+
&& offset + 2 < length
344+
&& sb.charAt(offset + 1) == '+'
345+
&& sb.charAt(offset + 2) == '\n') {
346+
/* Space followed by + and newline is AsciiDoc hard break that we consider whitespace */
347+
offset += 3;
348+
continue;
349+
} else if (ch > ' ') {
350+
/* Non-whitespace as defined by String.trim() */
351+
break;
352+
}
353+
offset++;
354+
}
355+
if (offset > 0) {
356+
sb.delete(0, offset);
357+
}
358+
if (sb.length() > 0) {
359+
offset = sb.length() - 1;
360+
while (offset >= 0) {
361+
final char ch = sb.charAt(offset);
362+
if (ch == '\n'
363+
&& offset - 2 >= 0
364+
&& sb.charAt(offset - 1) == '+'
365+
&& sb.charAt(offset - 2) == ' ') {
366+
/* Space followed by + is AsciiDoc hard break that we consider whitespace */
367+
offset -= 3;
368+
continue;
369+
} else if (ch > ' ') {
370+
/* Non-whitespace as defined by String.trim() */
371+
break;
372+
}
373+
offset--;
374+
}
375+
if (offset < sb.length() - 1) {
376+
sb.setLength(offset + 1);
377+
}
378+
}
379+
return sb.toString();
380+
}
381+
382+
private static StringBuilder newLineIfNeeded(StringBuilder sb) {
383+
trimText(sb, " \t\r\n");
384+
return sb.append(NEW_LINE);
385+
}
386+
387+
private static StringBuilder newLine(StringBuilder sb) {
388+
/* Trim trailing spaces and tabs at the end of line */
389+
trimText(sb, " \t");
390+
return sb.append(NEW_LINE);
391+
}
392+
393+
private static StringBuilder trimText(StringBuilder sb, String charsToTrim) {
394+
while (sb.length() > 0 && charsToTrim.indexOf(sb.charAt(sb.length() - 1)) >= 0) {
395+
sb.setLength(sb.length() - 1);
396+
}
397+
return sb;
398+
}
399+
400+
private StringBuilder unescapeHtmlEntities(StringBuilder sb, String text) {
401+
int i = 0;
402+
/* trim leading whitespace */
403+
LOOP: while (i < text.length()) {
404+
switch (text.charAt(i++)) {
405+
case ' ':
406+
case '\t':
407+
case '\r':
408+
case '\n':
409+
break;
410+
default:
411+
i--;
412+
break LOOP;
413+
}
414+
}
415+
for (; i < text.length(); i++) {
416+
final char ch = text.charAt(i);
417+
switch (ch) {
418+
case '&':
419+
int start = ++i;
420+
while (i < text.length() && text.charAt(i) != ';') {
421+
i++;
422+
}
423+
if (i > start) {
424+
final String abbrev = text.substring(start, i);
425+
switch (abbrev) {
426+
case "lt":
427+
sb.append('<');
428+
break;
429+
case "gt":
430+
sb.append('>');
431+
break;
432+
case "nbsp":
433+
sb.append("{nbsp}");
434+
break;
435+
case "amp":
436+
sb.append('&');
437+
break;
438+
default:
439+
try {
440+
int code = Integer.parseInt(abbrev);
441+
sb.append((char) code);
442+
} catch (NumberFormatException e) {
443+
throw new RuntimeException(
444+
"Could not parse HTML entity &" + abbrev + "; in\n\n" + text + "\n\n");
445+
}
446+
break;
447+
}
448+
}
449+
break;
450+
case '\r':
451+
if (i + 1 < text.length() && text.charAt(i + 1) == '\n') {
452+
/* Ignore \r followed by \n */
453+
} else {
454+
/* A Mac single \r: replace by \n */
455+
sb.append('\n');
456+
}
457+
break;
458+
default:
459+
sb.append(ch);
460+
461+
}
462+
}
463+
return sb;
464+
}
465+
298466
private StringBuilder appendEscapedAsciiDoc(StringBuilder sb, String text) {
299467
boolean escaping = false;
300468
for (int i = 0; i < text.length(); i++) {

core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/JavaDocConfigDescriptionParserTest.java

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public void parseNullJavaDoc() {
2828
@Test
2929
public void removeParagraphIndentation() {
3030
String parsed = parser.parseConfigDescription("First paragraph<br><br> Second Paragraph");
31-
assertEquals("First paragraph\n\nSecond Paragraph", parsed);
31+
assertEquals("First paragraph +\n +\nSecond Paragraph", parsed);
3232
}
3333

3434
@Test
@@ -50,13 +50,13 @@ public void parseSimpleJavaDoc() {
5050
@Test
5151
public void parseJavaDocWithParagraph() {
5252
String javaDoc = "hello<p>world</p>";
53-
String expectedOutput = "hello\nworld";
53+
String expectedOutput = "hello\n\nworld";
5454
String parsed = parser.parseConfigDescription(javaDoc);
5555

5656
assertEquals(expectedOutput, parsed);
5757

5858
javaDoc = "hello world<p>bonjour </p><p>le monde</p>";
59-
expectedOutput = "hello world\nbonjour \nle monde";
59+
expectedOutput = "hello world\n\nbonjour\n\nle monde";
6060
parsed = parser.parseConfigDescription(javaDoc);
6161

6262
assertEquals(expectedOutput, parsed);
@@ -118,21 +118,6 @@ public void parseJavaDocWithStyles() {
118118
assertEquals(expectedOutput, parsed);
119119
}
120120

121-
@Test
122-
public void parseJavaDocWithUlTags() {
123-
String javaDoc = "hello <ul>world</ul>";
124-
String expectedOutput = "hello world";
125-
String parsed = parser.parseConfigDescription(javaDoc);
126-
127-
assertEquals(expectedOutput, parsed);
128-
129-
javaDoc = "hello world<ul> bonjour </ul><ul>le monde</ul>";
130-
expectedOutput = "hello world bonjour le monde";
131-
parsed = parser.parseConfigDescription(javaDoc);
132-
133-
assertEquals(expectedOutput, parsed);
134-
}
135-
136121
@Test
137122
public void parseJavaDocWithLiTagsInsideUlTag() {
138123
String javaDoc = "List:" +
@@ -141,7 +126,7 @@ public void parseJavaDocWithLiTagsInsideUlTag() {
141126
"<li>2</li>\n" +
142127
"</ul>" +
143128
"";
144-
String expectedOutput = "List: \n - 1 \n - 2";
129+
String expectedOutput = "List:\n\n - 1\n - 2";
145130
String parsed = parser.parseConfigDescription(javaDoc);
146131

147132
assertEquals(expectedOutput, parsed);
@@ -155,7 +140,7 @@ public void parseJavaDocWithLiTagsInsideOlTag() {
155140
"<li>2</li>\n" +
156141
"</ol>" +
157142
"";
158-
String expectedOutput = "List: \n . 1 \n . 2";
143+
String expectedOutput = "List:\n\n . 1\n . 2";
159144
String parsed = parser.parseConfigDescription(javaDoc);
160145

161146
assertEquals(expectedOutput, parsed);
@@ -224,6 +209,49 @@ public void parseJavaDocWithUnknownNode() {
224209
assertEquals(expectedOutput, parsed);
225210
}
226211

212+
@Test
213+
public void parseJavaDocWithBlockquoteBlock() {
214+
assertEquals("See Section 4.5.5 of the JSR 380 specification, specifically\n"
215+
+ "\n"
216+
+ "[quote]\n"
217+
+ "____\n"
218+
+ "In sub types (be it sub classes/interfaces or interface implementations), no parameter constraints may be declared on overridden or implemented methods, nor may parameters be marked for cascaded validation. This would pose a strengthening of preconditions to be fulfilled by the caller.\n"
219+
+ "____\n"
220+
+ "\n"
221+
+ "That was interesting, wasn't it?",
222+
parser.parseConfigDescription("See Section 4.5.5 of the JSR 380 specification, specifically\n"
223+
+ "\n"
224+
+ "<blockquote>\n"
225+
+ "In sub types (be it sub classes/interfaces or interface implementations), no parameter constraints may\n"
226+
+ "be declared on overridden or implemented methods, nor may parameters be marked for cascaded validation.\n"
227+
+ "This would pose a strengthening of preconditions to be fulfilled by the caller.\n"
228+
+ "</blockquote>\nThat was interesting, wasn't it?"));
229+
230+
assertEquals(
231+
"Some HTML entities & special characters:\n\n```\n<os>|<arch>[/variant]|<os>/<arch>[/variant]\n```\n\nbaz",
232+
parser.parseConfigDescription(
233+
"Some HTML entities &amp; special characters:\n\n<pre>&lt;os&gt;|&lt;arch&gt;[/variant]|&lt;os&gt;/&lt;arch&gt;[/variant]\n</pre>\n\nbaz"));
234+
235+
// TODO
236+
// assertEquals("Example:\n\n```\nfoo\nbar\n```",
237+
// parser.parseConfigDescription("Example:\n\n<pre>{@code\nfoo\nbar\n}</pre>"));
238+
}
239+
240+
@Test
241+
public void parseJavaDocWithCodeBlock() {
242+
assertEquals("Example:\n\n```\nfoo\nbar\n```\n\nbaz",
243+
parser.parseConfigDescription("Example:\n\n<pre>\nfoo\nbar\n</pre>\n\nbaz"));
244+
245+
assertEquals(
246+
"Some HTML entities & special characters:\n\n```\n<os>|<arch>[/variant]|<os>/<arch>[/variant]\n```\n\nbaz",
247+
parser.parseConfigDescription(
248+
"Some HTML entities &amp; special characters:\n\n<pre>&lt;os&gt;|&lt;arch&gt;[/variant]|&lt;os&gt;/&lt;arch&gt;[/variant]\n</pre>\n\nbaz"));
249+
250+
// TODO
251+
// assertEquals("Example:\n\n```\nfoo\nbar\n```",
252+
// parser.parseConfigDescription("Example:\n\n<pre>{@code\nfoo\nbar\n}</pre>"));
253+
}
254+
227255
@Test
228256
public void asciidoc() {
229257
String asciidoc = "== My Asciidoc\n" +
@@ -308,4 +336,25 @@ public void escapeBrackets(String ch) {
308336
assertEquals(expected, actual);
309337
}
310338

339+
@Test
340+
void trim() {
341+
assertEquals("+ \nfoo", JavaDocParser.trim(new StringBuilder("+ \nfoo")));
342+
assertEquals("+", JavaDocParser.trim(new StringBuilder(" +")));
343+
assertEquals("foo", JavaDocParser.trim(new StringBuilder(" +\nfoo")));
344+
assertEquals("foo +", JavaDocParser.trim(new StringBuilder("foo +")));
345+
assertEquals("foo", JavaDocParser.trim(new StringBuilder("foo")));
346+
assertEquals("+", JavaDocParser.trim(new StringBuilder("+ \n")));
347+
assertEquals("+", JavaDocParser.trim(new StringBuilder(" +\n+ \n")));
348+
assertEquals("", JavaDocParser.trim(new StringBuilder(" +\n")));
349+
assertEquals("foo", JavaDocParser.trim(new StringBuilder(" \n\tfoo")));
350+
assertEquals("foo", JavaDocParser.trim(new StringBuilder("foo \n\t")));
351+
assertEquals("foo", JavaDocParser.trim(new StringBuilder(" \n\tfoo \n\t")));
352+
assertEquals("", JavaDocParser.trim(new StringBuilder("")));
353+
assertEquals("", JavaDocParser.trim(new StringBuilder(" \n\t")));
354+
assertEquals("+", JavaDocParser.trim(new StringBuilder(" +")));
355+
assertEquals("", JavaDocParser.trim(new StringBuilder(" +\n")));
356+
assertEquals("", JavaDocParser.trim(new StringBuilder(" +\n +\n")));
357+
assertEquals("foo +\nbar", JavaDocParser.trim(new StringBuilder(" foo +\nbar +\n")));
358+
}
359+
311360
}

extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,13 @@ public class JibConfig {
147147
* List of target platforms. Each platform is defined using the pattern:
148148
*
149149
* <pre>
150-
* {@literal <os>|<arch>[/variant]|<os>/<arch>[/variant]}
150+
* &lt;os>|&lt;arch>[/variant]|&lt;os>/&lt;arch>[/variant]
151151
* </pre>
152152
*
153153
* for example:
154154
*
155155
* <pre>
156-
* {@literal linux/amd64,linux/arm64/v8}
156+
* linux/amd64,linux/arm64/v8
157157
* </pre>
158158
*
159159
* If not specified, OS default is linux and architecture default is {@code amd64}.

0 commit comments

Comments
 (0)