Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,26 @@ private String getPlainText(Element element) {

// the formatting rules, implemented in a breadth-first DOM traverse
private static class FormattingVisitor implements NodeVisitor {
// Placeholder for indentation spaces that must survive the whitespace cleanup in toString(), which
// otherwise collates any run of real spaces -- including nested <li> indentation -- down to one.
private static final char INDENT_MARKER = '\u0001';

private StringBuilder accum = new StringBuilder(); // holds the accumulated text
private int listDepth = 0; // nesting depth of the current <ul>/<ol>, used to indent nested <li> items

// hit when the node is first seen
public void head(Node node, int depth) {
String name = node.nodeName();
if (node instanceof TextNode) {
accum.append(((TextNode) node).text()); // TextNodes carry all user-readable text in the DOM.
} else if (name.equals("li")) {
accum.append("\n * ");
accum.append('\n');
for (int i = 1; i < listDepth; i++) {
accum.append(INDENT_MARKER).append(INDENT_MARKER);
}
accum.append("* ");
} else if (name.equals("ul") || name.equals("ol")) {
listDepth++;
} else if (name.equals("dt")) {
accum.append(" ");
} else if (StringUtil.in(name, "p", "h1", "h2", "h3", "h4", "h5", "tr")) {
Expand All @@ -78,7 +89,9 @@ public void head(Node node, int depth) {
// hit when all of the node's children (if any) have been visited
public void tail(Node node, int depth) {
String name = node.nodeName();
if (StringUtil.in(name, "br", "dd", "dt", "p", "h1", "h2", "h3", "h4", "h5")) {
if (name.equals("ul") || name.equals("ol")) {
listDepth = Math.max(0, listDepth - 1);
} else if (StringUtil.in(name, "br", "dd", "dt", "p", "h1", "h2", "h3", "h4", "h5")) {
accum.append("\n");
} else if (name.equals("a")) {
// link is empty if it cannot be made absolute
Expand All @@ -91,8 +104,12 @@ public void tail(Node node, int depth) {

@Override
public String toString() {
// collate multiple consecutive spaces
return accum.toString().replaceAll(" +", " ").replace("\n ", "\n");
// collate multiple consecutive spaces, then turn the indentation placeholders -- untouched by the
// collation above since they aren't spaces -- into the real spaces they stand for.
return accum.toString()
.replaceAll(" +", " ")
.replace("\n ", "\n")
.replace(String.valueOf(INDENT_MARKER), " ");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,22 @@ void wrongHtml() {
"Generates something for the project.",
converter.convert("Generates <i>something</i> <b>for the project."));
}

@Test
void singleLevelList() {
String test = "<ul><li>Item one</li><li>Item two</li></ul>";
assertEquals("\n* Item one\n* Item two", converter.convert(test));
}

@Test
void nestedListIsIndented() {
String test = "<ul><li>Outer one<ul><li>Inner one</li><li>Inner two</li></ul></li><li>Outer two</li></ul>";
assertEquals("\n* Outer one\n * Inner one\n * Inner two\n* Outer two", converter.convert(test));
}

@Test
void doublyNestedListIndentsFurther() {
String test = "<ul><li>Level one<ul><li>Level two<ul><li>Level three</li></ul></li></ul></li></ul>";
assertEquals("\n* Level one\n * Level two\n * Level three", converter.convert(test));
}
}
Loading