From d34a4b0016110ada3d247ca575fde2a0899785a7 Mon Sep 17 00:00:00 2001
From: "Piotr P. Karwasz"
Date: Thu, 8 Feb 2024 09:36:17 +0100
Subject: [PATCH] Add support for emphasis and code tags
---
.../AbstractAsciidocTreeVisitor.java | 65 ++++++++++---
.../log4j/docgen/processor/AsciidocData.java | 94 +++++++++++++------
.../docgen/processor/internal/BlockImpl.java | 1 -
.../src/test/it/example/JavadocExample.java | 7 ++
.../expected/processor/JavadocExample.adoc | 5 +
5 files changed, 129 insertions(+), 43 deletions(-)
diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java
index 00b4fbe8..609cca7d 100644
--- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java
+++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AbstractAsciidocTreeVisitor.java
@@ -43,18 +43,25 @@
import org.asciidoctor.ast.StructuralNode;
import org.asciidoctor.ast.Table;
-class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor {
+abstract class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor {
+
+ private static final String SOURCE_STYLE = "source";
+ // These are not supported by AsciiDoctor and are only used internally
+ private static final String CODE_STYLE = "code";
+ private static final String CODE_DELIM = "`";
+ private static final String EMPHASIS_STYLE = "em";
+ private static final String EMPHASIS_DELIM = "_";
+ private static final String STRONG_EMPHASIS_STYLE = "strong";
+ private static final String STRONG_EMPHASIS_DELIM = "*";
private static void appendSentences(final String text, final AsciidocData data) {
- final String body = StringUtils.normalizeSpace(text);
- final String[] sentences = body.split("(?<=\\w{2}[.!?])", -1);
+ final String[] sentences = text.split("(?<=\\w{2}[.!?])", -1);
// Full sentences
for (int i = 0; i < sentences.length - 1; i++) {
- data.appendWords(sentences[i].strip());
- data.newLine();
+ data.appendAdjustingSpace(sentences[i]).newLine();
}
// Partial sentence
- data.appendWords(sentences[sentences.length - 1].strip());
+ data.appendAdjustingSpace(sentences[sentences.length - 1]);
}
@Override
@@ -112,9 +119,19 @@ public Void visitStartElement(final StartElementTree node, final AsciidocData da
break;
case "pre":
data.newParagraph();
- final Block currentParagraph = data.getCurrentParagraph();
- currentParagraph.setContext(BlockImpl.LISTING_CONTEXT);
- currentParagraph.setStyle(BlockImpl.SOURCE_STYLE);
+ data.getCurrentParagraph().setContext(BlockImpl.LISTING_CONTEXT);
+ data.getCurrentParagraph().setStyle(SOURCE_STYLE);
+ break;
+ case "code":
+ data.newTextSpan(CODE_STYLE);
+ break;
+ case "em":
+ case "i":
+ data.newTextSpan(EMPHASIS_STYLE);
+ break;
+ case "strong":
+ case "b":
+ data.newTextSpan(STRONG_EMPHASIS_STYLE);
break;
default:
}
@@ -187,6 +204,17 @@ public Void visitEndElement(final EndElementTree node, final AsciidocData data)
table.getBody().add(row);
}
break;
+ case "code":
+ appendSpan(data, CODE_DELIM);
+ break;
+ case "em":
+ case "i":
+ appendSpan(data, EMPHASIS_DELIM);
+ break;
+ case "strong":
+ case "b":
+ appendSpan(data, STRONG_EMPHASIS_DELIM);
+ break;
default:
}
return super.visitEndElement(node, data);
@@ -210,16 +238,27 @@ public Void visitLink(final LinkTree node, final AsciidocData data) {
@Override
public Void visitLiteral(final LiteralTree node, final AsciidocData data) {
if (node.getKind() == DocTree.Kind.CODE) {
- if (!data.getCurrentLine().isEmpty()) {
- data.append(" ");
- }
- data.append("`").append(node.getBody().getBody()).append("`");
+ data.newTextSpan(CODE_STYLE);
+ node.getBody().accept(this, data);
+ appendSpan(data, "`");
} else {
node.getBody().accept(this, data);
}
return super.visitLiteral(node, data);
}
+ private void appendSpan(final AsciidocData data, final String delimiter) {
+ final String body = data.popTextSpan();
+ data.append(delimiter);
+ final boolean needsEscaping = body.contains(delimiter);
+ if (needsEscaping) {
+ data.append("++").append(body).append("++");
+ } else {
+ data.append(body);
+ }
+ data.append(delimiter);
+ }
+
@Override
public Void visitText(final TextTree node, final AsciidocData data) {
final Block currentParagraph = data.getCurrentParagraph();
diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java
index 09cbe7a6..a254cb8a 100644
--- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java
+++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/AsciidocData.java
@@ -16,8 +16,13 @@
*/
package org.apache.logging.log4j.docgen.processor;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
import java.util.EmptyStackException;
+import java.util.List;
import java.util.function.Function;
+import java.util.regex.Pattern;
import org.apache.logging.log4j.docgen.processor.internal.BlockImpl;
import org.apache.logging.log4j.docgen.processor.internal.DocumentImpl;
import org.apache.logging.log4j.docgen.processor.internal.SectionImpl;
@@ -26,35 +31,41 @@
import org.asciidoctor.ast.StructuralNode;
final class AsciidocData {
+ private static final Pattern WHITESPACE_SEQUENCE = Pattern.compile("\\s+");
+ private static final String SPACE = " ";
+ private static final char SPACE_CHAR = ' ';
+ private static final char CODE_CHAR = '`';
+ private static final String NEW_LINE = "\n";
+
private final Document document;
private int currentSectionLevel;
private StructuralNode currentNode;
- // not attached to the current node
- private Block currentParagraph;
- private final StringBuilder currentLine;
+ // A stack of nested text blocks. Each can have a different style.
+ private final Deque paragraphs = new ArrayDeque<>();
+ private final Deque lines = new ArrayDeque<>();
public AsciidocData() {
document = new DocumentImpl();
currentSectionLevel = 1;
currentNode = document;
- currentParagraph = new BlockImpl(currentNode);
- currentLine = new StringBuilder();
+ paragraphs.push(new BlockImpl(currentNode));
+ lines.push(new StringBuilder());
}
public void newLine() {
// Remove trailing space
- final String line = currentLine.toString().stripTrailing();
+ final String line = getCurrentLine().toString().stripTrailing();
// Ignore leading empty lines
- if (!currentParagraph.getLines().isEmpty() || !line.isEmpty()) {
- currentParagraph.getLines().add(line);
+ if (!getCurrentParagraph().getLines().isEmpty() || !line.isEmpty()) {
+ getCurrentParagraph().getLines().add(line);
}
- currentLine.setLength(0);
+ getCurrentLine().setLength(0);
}
public AsciidocData append(final String text) {
final String[] lines = text.split("\r?\n", -1);
for (int i = 0; i < lines.length; i++) {
- currentLine.append(lines[i]);
+ getCurrentLine().append(lines[i]);
if (i != lines.length - 1) {
newLine();
}
@@ -62,19 +73,48 @@ public AsciidocData append(final String text) {
return this;
}
- public void appendWords(final String words) {
- if (words.isBlank()) {
- return;
+ public AsciidocData appendAdjustingSpace(final CharSequence text) {
+ final String normalized = WHITESPACE_SEQUENCE.matcher(text).replaceAll(SPACE);
+ if (!normalized.isEmpty()) {
+ final StringBuilder currentLine = getCurrentLine();
+ // Last char of current line or space
+ final char lineLastChar = currentLine.isEmpty() ? SPACE_CHAR : currentLine.charAt(currentLine.length() - 1);
+ // First char of test
+ final char textFirstChar = normalized.charAt(0);
+ if (lineLastChar == SPACE_CHAR && textFirstChar == SPACE_CHAR) {
+ // Merge spaces
+ currentLine.append(normalized, 1, normalized.length());
+ } else if (lineLastChar == CODE_CHAR && Character.isAlphabetic(textFirstChar)) {
+ currentLine.append(SPACE_CHAR).append(normalized);
+ } else {
+ currentLine.append(normalized);
+ }
}
- // Separate text from previous words
- if (!currentLine.isEmpty() && Character.isAlphabetic(words.codePointAt(0))) {
- currentLine.append(" ");
+ return this;
+ }
+
+ public void newTextSpan(final String style) {
+ paragraphs.push(new BlockImpl(paragraphs.peek()));
+ lines.push(new StringBuilder());
+ }
+
+ public String popTextSpan() {
+ // Flush the paragraph
+ final StringBuilder line = lines.peek();
+ if (line != null && !line.isEmpty()) {
+ newLine();
}
- currentLine.append(words);
+ lines.pop();
+ return String.join(SPACE, paragraphs.pop().getLines());
}
public void newParagraph() {
+ newParagraph(currentNode);
+ }
+
+ private void newParagraph(final StructuralNode parent) {
newLine();
+ final Block currentParagraph = paragraphs.pop();
final java.util.List lines = currentParagraph.getLines();
// Remove trailing empty lines
for (int i = lines.size() - 1; i >= 0; i--) {
@@ -84,8 +124,8 @@ public void newParagraph() {
}
if (!currentParagraph.getLines().isEmpty()) {
currentNode.append(currentParagraph);
- currentParagraph = new BlockImpl(currentNode);
}
+ paragraphs.push(new BlockImpl(parent));
}
public StructuralNode getCurrentNode() {
@@ -93,11 +133,11 @@ public StructuralNode getCurrentNode() {
}
public Block getCurrentParagraph() {
- return currentParagraph;
+ return paragraphs.peek();
}
public StringBuilder getCurrentLine() {
- return currentLine;
+ return lines.peek();
}
public Document getDocument() {
@@ -121,12 +161,10 @@ public void setCurrentSectionLevel(final int sectionLevel) {
* @param supplier a function to create a new node that takes its parent node a parameter.
*/
public StructuralNode pushChildNode(final Function super StructuralNode, ? extends StructuralNode> supplier) {
- // Flushes the current paragraph
- newParagraph();
-
final StructuralNode child = supplier.apply(currentNode);
- // Creates a new current paragraph
- currentParagraph = new BlockImpl(child);
+
+ // Flushes and reparents the current paragraph
+ newParagraph(child);
currentNode.append(child);
return currentNode = child;
@@ -134,15 +172,13 @@ public StructuralNode pushChildNode(final Function super StructuralNode, ? ext
public void popNode() {
final StructuralNode currentNode = this.currentNode;
- // Flushes the current paragraph
- newParagraph();
final StructuralNode parent = (StructuralNode) currentNode.getParent();
if (parent == null) {
throw new EmptyStackException();
}
- // Creates a new current paragraph
- currentParagraph = new BlockImpl(parent);
+ // Flushes and creates a new current paragraph
+ newParagraph(parent);
this.currentNode = parent;
}
diff --git a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/BlockImpl.java b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/BlockImpl.java
index a2dbfd8d..0de58bd3 100644
--- a/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/BlockImpl.java
+++ b/log4j-docgen/src/main/java/org/apache/logging/log4j/docgen/processor/internal/BlockImpl.java
@@ -25,7 +25,6 @@ public class BlockImpl extends StructuralNodeImpl implements Block {
public static final String PARAGRAPH_CONTEXT = "paragraph";
public static final String LISTING_CONTEXT = "listing";
- public static final String SOURCE_STYLE = "source";
private List lines = new ArrayList<>();
diff --git a/log4j-docgen/src/test/it/example/JavadocExample.java b/log4j-docgen/src/test/it/example/JavadocExample.java
index 5ddf2c4c..bc76bfd0 100644
--- a/log4j-docgen/src/test/it/example/JavadocExample.java
+++ b/log4j-docgen/src/test/it/example/JavadocExample.java
@@ -23,6 +23,13 @@
* paragraph has two sentences.
*
*
+ * A sentence with foo
, foo`
, foo
bar. Another sentence with {@code foo},
+ * {@code foo`}, {@code foo}bar.
+ *
+ *
+ * We can use strong emphasis too, or we can use bold and italic.
+ *
+ *
* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum blandit dictum sem, ornare posuere lorem
* convallis sit amet. Sed dui augue, faucibus ut nisi id, mollis euismod nibh. Donec lobortis luctus viverra. In
* orci ante, pretium et fringilla at, sagittis nec justo. Cras finibus lorem vel volutpat interdum. Sed laoreet
diff --git a/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc
index 1afe6570..9986f396 100644
--- a/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc
+++ b/log4j-docgen/src/test/resources/expected/processor/JavadocExample.adoc
@@ -19,6 +19,11 @@ Example of JavaDoc to AsciiDoc conversion
We run the `javadoc` tool on this class to test conversion of JavaDoc comments to AsciiDoc.
This paragraph has two sentences.
+A sentence with `foo`, `++foo`++`, `foo` bar.
+Another sentence with `foo`, `++foo`++`, `foo` bar.
+
+We can use *strong* _emphasis_ too, or we can use *bold* and _italic_.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Vestibulum blandit dictum sem, ornare posuere lorem convallis sit amet.
Sed dui augue, faucibus ut nisi id, mollis euismod nibh.