Skip to content

Commit d34a4b0

Browse files
committed
Add support for emphasis and code tags
1 parent 6e01fa9 commit d34a4b0

File tree

5 files changed

+129
-43
lines changed

5 files changed

+129
-43
lines changed

log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java

+52-13
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,25 @@
4343
import org.asciidoctor.ast.StructuralNode;
4444
import org.asciidoctor.ast.Table;
4545

46-
class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor<Void, AsciidocData> {
46+
abstract class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor<Void, AsciidocData> {
47+
48+
private static final String SOURCE_STYLE = "source";
49+
// These are not supported by AsciiDoctor and are only used internally
50+
private static final String CODE_STYLE = "code";
51+
private static final String CODE_DELIM = "`";
52+
private static final String EMPHASIS_STYLE = "em";
53+
private static final String EMPHASIS_DELIM = "_";
54+
private static final String STRONG_EMPHASIS_STYLE = "strong";
55+
private static final String STRONG_EMPHASIS_DELIM = "*";
4756

4857
private static void appendSentences(final String text, final AsciidocData data) {
49-
final String body = StringUtils.normalizeSpace(text);
50-
final String[] sentences = body.split("(?<=\\w{2}[.!?])", -1);
58+
final String[] sentences = text.split("(?<=\\w{2}[.!?])", -1);
5159
// Full sentences
5260
for (int i = 0; i < sentences.length - 1; i++) {
53-
data.appendWords(sentences[i].strip());
54-
data.newLine();
61+
data.appendAdjustingSpace(sentences[i]).newLine();
5562
}
5663
// Partial sentence
57-
data.appendWords(sentences[sentences.length - 1].strip());
64+
data.appendAdjustingSpace(sentences[sentences.length - 1]);
5865
}
5966

6067
@Override
@@ -112,9 +119,19 @@ public Void visitStartElement(final StartElementTree node, final AsciidocData da
112119
break;
113120
case "pre":
114121
data.newParagraph();
115-
final Block currentParagraph = data.getCurrentParagraph();
116-
currentParagraph.setContext(BlockImpl.LISTING_CONTEXT);
117-
currentParagraph.setStyle(BlockImpl.SOURCE_STYLE);
122+
data.getCurrentParagraph().setContext(BlockImpl.LISTING_CONTEXT);
123+
data.getCurrentParagraph().setStyle(SOURCE_STYLE);
124+
break;
125+
case "code":
126+
data.newTextSpan(CODE_STYLE);
127+
break;
128+
case "em":
129+
case "i":
130+
data.newTextSpan(EMPHASIS_STYLE);
131+
break;
132+
case "strong":
133+
case "b":
134+
data.newTextSpan(STRONG_EMPHASIS_STYLE);
118135
break;
119136
default:
120137
}
@@ -187,6 +204,17 @@ public Void visitEndElement(final EndElementTree node, final AsciidocData data)
187204
table.getBody().add(row);
188205
}
189206
break;
207+
case "code":
208+
appendSpan(data, CODE_DELIM);
209+
break;
210+
case "em":
211+
case "i":
212+
appendSpan(data, EMPHASIS_DELIM);
213+
break;
214+
case "strong":
215+
case "b":
216+
appendSpan(data, STRONG_EMPHASIS_DELIM);
217+
break;
190218
default:
191219
}
192220
return super.visitEndElement(node, data);
@@ -210,16 +238,27 @@ public Void visitLink(final LinkTree node, final AsciidocData data) {
210238
@Override
211239
public Void visitLiteral(final LiteralTree node, final AsciidocData data) {
212240
if (node.getKind() == DocTree.Kind.CODE) {
213-
if (!data.getCurrentLine().isEmpty()) {
214-
data.append(" ");
215-
}
216-
data.append("`").append(node.getBody().getBody()).append("`");
241+
data.newTextSpan(CODE_STYLE);
242+
node.getBody().accept(this, data);
243+
appendSpan(data, "`");
217244
} else {
218245
node.getBody().accept(this, data);
219246
}
220247
return super.visitLiteral(node, data);
221248
}
222249

250+
private void appendSpan(final AsciidocData data, final String delimiter) {
251+
final String body = data.popTextSpan();
252+
data.append(delimiter);
253+
final boolean needsEscaping = body.contains(delimiter);
254+
if (needsEscaping) {
255+
data.append("++").append(body).append("++");
256+
} else {
257+
data.append(body);
258+
}
259+
data.append(delimiter);
260+
}
261+
223262
@Override
224263
public Void visitText(final TextTree node, final AsciidocData data) {
225264
final Block currentParagraph = data.getCurrentParagraph();

log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java

+65-29
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616
*/
1717
package org.apache.logging.log4j.docgen.processor;
1818

19+
import java.util.ArrayDeque;
20+
import java.util.ArrayList;
21+
import java.util.Deque;
1922
import java.util.EmptyStackException;
23+
import java.util.List;
2024
import java.util.function.Function;
25+
import java.util.regex.Pattern;
2126
import org.apache.logging.log4j.docgen.processor.internal.BlockImpl;
2227
import org.apache.logging.log4j.docgen.processor.internal.DocumentImpl;
2328
import org.apache.logging.log4j.docgen.processor.internal.SectionImpl;
@@ -26,55 +31,90 @@
2631
import org.asciidoctor.ast.StructuralNode;
2732

2833
final class AsciidocData {
34+
private static final Pattern WHITESPACE_SEQUENCE = Pattern.compile("\\s+");
35+
private static final String SPACE = " ";
36+
private static final char SPACE_CHAR = ' ';
37+
private static final char CODE_CHAR = '`';
38+
private static final String NEW_LINE = "\n";
39+
2940
private final Document document;
3041
private int currentSectionLevel;
3142
private StructuralNode currentNode;
32-
// not attached to the current node
33-
private Block currentParagraph;
34-
private final StringBuilder currentLine;
43+
// A stack of nested text blocks. Each can have a different style.
44+
private final Deque<Block> paragraphs = new ArrayDeque<>();
45+
private final Deque<StringBuilder> lines = new ArrayDeque<>();
3546

3647
public AsciidocData() {
3748
document = new DocumentImpl();
3849
currentSectionLevel = 1;
3950
currentNode = document;
40-
currentParagraph = new BlockImpl(currentNode);
41-
currentLine = new StringBuilder();
51+
paragraphs.push(new BlockImpl(currentNode));
52+
lines.push(new StringBuilder());
4253
}
4354

4455
public void newLine() {
4556
// Remove trailing space
46-
final String line = currentLine.toString().stripTrailing();
57+
final String line = getCurrentLine().toString().stripTrailing();
4758
// Ignore leading empty lines
48-
if (!currentParagraph.getLines().isEmpty() || !line.isEmpty()) {
49-
currentParagraph.getLines().add(line);
59+
if (!getCurrentParagraph().getLines().isEmpty() || !line.isEmpty()) {
60+
getCurrentParagraph().getLines().add(line);
5061
}
51-
currentLine.setLength(0);
62+
getCurrentLine().setLength(0);
5263
}
5364

5465
public AsciidocData append(final String text) {
5566
final String[] lines = text.split("\r?\n", -1);
5667
for (int i = 0; i < lines.length; i++) {
57-
currentLine.append(lines[i]);
68+
getCurrentLine().append(lines[i]);
5869
if (i != lines.length - 1) {
5970
newLine();
6071
}
6172
}
6273
return this;
6374
}
6475

65-
public void appendWords(final String words) {
66-
if (words.isBlank()) {
67-
return;
76+
public AsciidocData appendAdjustingSpace(final CharSequence text) {
77+
final String normalized = WHITESPACE_SEQUENCE.matcher(text).replaceAll(SPACE);
78+
if (!normalized.isEmpty()) {
79+
final StringBuilder currentLine = getCurrentLine();
80+
// Last char of current line or space
81+
final char lineLastChar = currentLine.isEmpty() ? SPACE_CHAR : currentLine.charAt(currentLine.length() - 1);
82+
// First char of test
83+
final char textFirstChar = normalized.charAt(0);
84+
if (lineLastChar == SPACE_CHAR && textFirstChar == SPACE_CHAR) {
85+
// Merge spaces
86+
currentLine.append(normalized, 1, normalized.length());
87+
} else if (lineLastChar == CODE_CHAR && Character.isAlphabetic(textFirstChar)) {
88+
currentLine.append(SPACE_CHAR).append(normalized);
89+
} else {
90+
currentLine.append(normalized);
91+
}
6892
}
69-
// Separate text from previous words
70-
if (!currentLine.isEmpty() && Character.isAlphabetic(words.codePointAt(0))) {
71-
currentLine.append(" ");
93+
return this;
94+
}
95+
96+
public void newTextSpan(final String style) {
97+
paragraphs.push(new BlockImpl(paragraphs.peek()));
98+
lines.push(new StringBuilder());
99+
}
100+
101+
public String popTextSpan() {
102+
// Flush the paragraph
103+
final StringBuilder line = lines.peek();
104+
if (line != null && !line.isEmpty()) {
105+
newLine();
72106
}
73-
currentLine.append(words);
107+
lines.pop();
108+
return String.join(SPACE, paragraphs.pop().getLines());
74109
}
75110

76111
public void newParagraph() {
112+
newParagraph(currentNode);
113+
}
114+
115+
private void newParagraph(final StructuralNode parent) {
77116
newLine();
117+
final Block currentParagraph = paragraphs.pop();
78118
final java.util.List<String> lines = currentParagraph.getLines();
79119
// Remove trailing empty lines
80120
for (int i = lines.size() - 1; i >= 0; i--) {
@@ -84,20 +124,20 @@ public void newParagraph() {
84124
}
85125
if (!currentParagraph.getLines().isEmpty()) {
86126
currentNode.append(currentParagraph);
87-
currentParagraph = new BlockImpl(currentNode);
88127
}
128+
paragraphs.push(new BlockImpl(parent));
89129
}
90130

91131
public StructuralNode getCurrentNode() {
92132
return currentNode;
93133
}
94134

95135
public Block getCurrentParagraph() {
96-
return currentParagraph;
136+
return paragraphs.peek();
97137
}
98138

99139
public StringBuilder getCurrentLine() {
100-
return currentLine;
140+
return lines.peek();
101141
}
102142

103143
public Document getDocument() {
@@ -121,28 +161,24 @@ public void setCurrentSectionLevel(final int sectionLevel) {
121161
* @param supplier a function to create a new node that takes its parent node a parameter.
122162
*/
123163
public StructuralNode pushChildNode(final Function<? super StructuralNode, ? extends StructuralNode> supplier) {
124-
// Flushes the current paragraph
125-
newParagraph();
126-
127164
final StructuralNode child = supplier.apply(currentNode);
128-
// Creates a new current paragraph
129-
currentParagraph = new BlockImpl(child);
165+
166+
// Flushes and reparents the current paragraph
167+
newParagraph(child);
130168

131169
currentNode.append(child);
132170
return currentNode = child;
133171
}
134172

135173
public void popNode() {
136174
final StructuralNode currentNode = this.currentNode;
137-
// Flushes the current paragraph
138-
newParagraph();
139175

140176
final StructuralNode parent = (StructuralNode) currentNode.getParent();
141177
if (parent == null) {
142178
throw new EmptyStackException();
143179
}
144-
// Creates a new current paragraph
145-
currentParagraph = new BlockImpl(parent);
180+
// Flushes and creates a new current paragraph
181+
newParagraph(parent);
146182

147183
this.currentNode = parent;
148184
}

log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/BlockImpl.java

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public class BlockImpl extends StructuralNodeImpl implements Block {
2525

2626
public static final String PARAGRAPH_CONTEXT = "paragraph";
2727
public static final String LISTING_CONTEXT = "listing";
28-
public static final String SOURCE_STYLE = "source";
2928

3029
private List<String> lines = new ArrayList<>();
3130

log4j-docgen/src/test/it/example/JavadocExample.java

+7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@
2323
* paragraph has two sentences.
2424
* </p>
2525
* <p>
26+
* A sentence with <code>foo</code>, <code>foo`</code>, <code>foo</code>bar. Another sentence with {@code foo},
27+
* {@code foo`}, {@code foo}bar.
28+
* </p>
29+
* <p>
30+
* We can use <strong>strong</strong> <em>emphasis</em> too, or we can use <b>bold</b> and <i>italic</i>.
31+
* </p>
32+
* <p>
2633
* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum blandit dictum sem, ornare posuere lorem
2734
* convallis sit amet. Sed dui augue, faucibus ut nisi id, mollis euismod nibh. Donec lobortis luctus viverra. In
2835
* orci ante, pretium et fringilla at, sagittis nec justo. Cras finibus lorem vel volutpat interdum. Sed laoreet

log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ Example of JavaDoc to AsciiDoc conversion
1919
We run the `javadoc` tool on this class to test conversion of JavaDoc comments to AsciiDoc.
2020
This paragraph has two sentences.
2121
22+
A sentence with `foo`, `++foo`++`, `foo` bar.
23+
Another sentence with `foo`, `++foo`++`, `foo` bar.
24+
25+
We can use *strong* _emphasis_ too, or we can use *bold* and _italic_.
26+
2227
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
2328
Vestibulum blandit dictum sem, ornare posuere lorem convallis sit amet.
2429
Sed dui augue, faucibus ut nisi id, mollis euismod nibh.

0 commit comments

Comments
 (0)