Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Javadoc to Asciidoc converter #101

Merged
merged 6 commits into from
Feb 8, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add support for emphasis and code tags
ppkarwasz committed Feb 8, 2024
commit aa56053f53359d642417a815fddb5d94b06b1fcd
Original file line number Diff line number Diff line change
@@ -43,18 +43,25 @@
import org.asciidoctor.ast.StructuralNode;
import org.asciidoctor.ast.Table;

class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor<Void, AsciidocData> {
abstract class AbstractAsciidocTreeVisitor extends SimpleDocTreeVisitor<Void, AsciidocData> {

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();
Original file line number Diff line number Diff line change
@@ -16,8 +16,11 @@
*/
package org.apache.logging.log4j.docgen.processor;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.EmptyStackException;
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,55 +29,90 @@
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<Block> paragraphs = new ArrayDeque<>();
private final Deque<StringBuilder> 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();
}
}
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<String> lines = currentParagraph.getLines();
// Remove trailing empty lines
for (int i = lines.size() - 1; i >= 0; i--) {
@@ -84,20 +122,20 @@ public void newParagraph() {
}
if (!currentParagraph.getLines().isEmpty()) {
currentNode.append(currentParagraph);
currentParagraph = new BlockImpl(currentNode);
}
paragraphs.push(new BlockImpl(parent));
}

public StructuralNode getCurrentNode() {
return currentNode;
}

public Block getCurrentParagraph() {
return currentParagraph;
return paragraphs.peek();
}

public StringBuilder getCurrentLine() {
return currentLine;
return lines.peek();
}

public Document getDocument() {
@@ -121,28 +159,24 @@ 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;
}

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;
}
Original file line number Diff line number Diff line change
@@ -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<String> lines = new ArrayList<>();

7 changes: 7 additions & 0 deletions log4j-docgen/src/test/it/example/JavadocExample.java
Original file line number Diff line number Diff line change
@@ -23,6 +23,13 @@
* paragraph has two sentences.
* </p>
* <p>
* A sentence with <code>foo</code>, <code>foo`</code>, <code>foo</code>bar. Another sentence with {@code foo},
* {@code foo`}, {@code foo}bar.
* </p>
* <p>
* We can use <strong>strong</strong> <em>emphasis</em> too, or we can use <b>bold</b> and <i>italic</i>.
* </p>
* <p>
* 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
Original file line number Diff line number Diff line change
@@ -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.