This is an automated email from the ASF dual-hosted git repository.
kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-doxia.git
The following commit(s) were added to refs/heads/master by this push:
new c93d1425 [DOXIA-740] IndexingSink: fix incomplete dispatching of Sink
events (#228)
c93d1425 is described below
commit c93d1425bd0203496d1e75ec49a41874a8d9f84e
Author: Konrad Windszus <[email protected]>
AuthorDate: Tue Sep 24 09:49:46 2024 +0200
[DOXIA-740] IndexingSink: fix incomplete dispatching of Sink events (#228)
IndexingSink was always expecting a sectionTitle below each section
which doesn't hold.
Also close bufferingSink when the section is ended or a new one started
(without a preceding sectionTitle)
Skip irrelevant index entries (non section type or invalid id) in TOC
---
.../org/apache/maven/doxia/index/IndexEntry.java | 16 ++++++-
.../org/apache/maven/doxia/index/IndexingSink.java | 24 ++++++++--
.../org/apache/maven/doxia/macro/toc/TocMacro.java | 27 +++++++----
.../sink/impl/CreateAnchorsForIndexEntries.java | 2 +-
.../apache/maven/doxia/index/IndexingSinkTest.java | 55 ++++++++++++++++++++++
.../apache/maven/doxia/macro/toc/TocMacroTest.java | 37 +++++++++++++++
.../doxia/module/markdown/MarkdownParserTest.java | 49 +++++++++++++++----
.../src/test/resources/headings.md | 7 +++
8 files changed, 195 insertions(+), 22 deletions(-)
diff --git
a/doxia-core/src/main/java/org/apache/maven/doxia/index/IndexEntry.java
b/doxia-core/src/main/java/org/apache/maven/doxia/index/IndexEntry.java
index a33198d8..92637cbe 100644
--- a/doxia-core/src/main/java/org/apache/maven/doxia/index/IndexEntry.java
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/index/IndexEntry.java
@@ -95,7 +95,11 @@ public class IndexEntry {
.findAny()
.orElseThrow(() -> new IllegalStateException("Could not
find enum for sectionLevel " + level));
}
- };
+
+ public boolean isSection() {
+ return sectionLevel >= 1;
+ }
+ }
/**
* The type of the entry, one of the types defined by {@link IndexingSink}
@@ -156,6 +160,14 @@ public class IndexEntry {
return id;
}
+ /**
+ * Returns if the entry has an id.
+ * @return {@code true} if the entry has a valid id, otherwise it can be
considered invalid/empty.
+ */
+ public boolean hasId() {
+ return id != null;
+ }
+
/**
* Set the id.
*
@@ -197,7 +209,7 @@ public class IndexEntry {
/**
* Returns the title.
*
- * @return the title.
+ * @return the title (may be {@code null}).
*/
public String getTitle() {
return title;
diff --git
a/doxia-core/src/main/java/org/apache/maven/doxia/index/IndexingSink.java
b/doxia-core/src/main/java/org/apache/maven/doxia/index/IndexingSink.java
index 9554de75..65071d56 100644
--- a/doxia-core/src/main/java/org/apache/maven/doxia/index/IndexingSink.java
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/index/IndexingSink.java
@@ -54,8 +54,14 @@ public class IndexingSink extends
org.apache.maven.doxia.sink.impl.SinkWrapper {
private final IndexEntry rootEntry;
+ /** Is {@code true} once the sink has been closed. */
private boolean isComplete;
+
private boolean isTitle;
+
+ /** Is {@code true} if the sink is currently populating entry data (i.e.
metadata about the current entry is not completely captured yet) */
+ private boolean hasOpenEntry;
+
/**
* @deprecated legacy constructor, use {@link #IndexingSink(Sink)} with
{@link SinkAdapter} as argument and call {@link #getRootEntry()} to retrieve
the index tree afterwards.
*/
@@ -123,12 +129,14 @@ public class IndexingSink extends
org.apache.maven.doxia.sink.impl.SinkWrapper {
@Override
public void section(int level, SinkEventAttributes attributes) {
super.section(level, attributes);
+ indexEntryComplete(); // make sure the previous entry is complete
this.type = IndexEntry.Type.fromSectionLevel(level);
pushNewEntry(type);
}
@Override
public void section_(int level) {
+ indexEntryComplete(); // make sure the previous entry is complete
pop();
super.section_(level);
}
@@ -150,6 +158,7 @@ public class IndexingSink extends
org.apache.maven.doxia.sink.impl.SinkWrapper {
case SECTION_3:
case SECTION_4:
case SECTION_5:
+ case SECTION_6:
//
-----------------------------------------------------------------------
// Sanitize the id. The most important step is to remove
any blanks
//
-----------------------------------------------------------------------
@@ -157,7 +166,12 @@ public class IndexingSink extends
org.apache.maven.doxia.sink.impl.SinkWrapper {
// append text to current entry
IndexEntry entry = stack.lastElement();
- String title = entry.getTitle() + text;
+ String title = entry.getTitle();
+ if (title != null) {
+ title += text;
+ } else {
+ title = text;
+ }
title = title.replaceAll("[\\r\\n]+", "");
entry.setTitle(title);
@@ -220,6 +234,9 @@ public class IndexingSink extends
org.apache.maven.doxia.sink.impl.SinkWrapper {
}
void indexEntryComplete() {
+ if (!hasOpenEntry) {
+ return;
+ }
this.type = Type.UNKNOWN;
// remove buffering sink from pipeline
BufferingSink bufferingSink =
BufferingSinkProxyFactory.castAsBufferingSink(getWrappedSink());
@@ -229,6 +246,7 @@ public class IndexingSink extends
org.apache.maven.doxia.sink.impl.SinkWrapper {
// flush the buffer afterwards
bufferingSink.flush();
+ hasOpenEntry = false;
}
/**
@@ -242,11 +260,11 @@ public class IndexingSink extends
org.apache.maven.doxia.sink.impl.SinkWrapper {
* Creates and pushes a new IndexEntry onto the top of this stack.
*/
private void pushNewEntry(Type type) {
- IndexEntry entry = new IndexEntry(peek(), "", type);
- entry.setTitle("");
+ IndexEntry entry = new IndexEntry(peek(), null, type);
stack.push(entry);
// now buffer everything till the next index metadata is complete
setWrappedSink(new
BufferingSinkProxyFactory().createWrapper(getWrappedSink()));
+ hasOpenEntry = true;
}
/**
diff --git
a/doxia-core/src/main/java/org/apache/maven/doxia/macro/toc/TocMacro.java
b/doxia-core/src/main/java/org/apache/maven/doxia/macro/toc/TocMacro.java
index d07771c2..bc7ba075 100644
--- a/doxia-core/src/main/java/org/apache/maven/doxia/macro/toc/TocMacro.java
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/macro/toc/TocMacro.java
@@ -31,6 +31,7 @@ import org.apache.maven.doxia.macro.MacroRequest;
import org.apache.maven.doxia.parser.ParseException;
import org.apache.maven.doxia.parser.Parser;
import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.doxia.sink.SinkEventAttributes;
import org.apache.maven.doxia.sink.impl.SinkAdapter;
import org.apache.maven.doxia.util.DoxiaUtils;
@@ -85,7 +86,7 @@ public class TocMacro extends AbstractMacro {
private int fromDepth;
/** End depth. */
- private int toDepth;
+ private int toDepth = DEFAULT_DEPTH;
/** The default end depth. */
private static final int DEFAULT_DEPTH = 5;
@@ -112,9 +113,13 @@ public class TocMacro extends AbstractMacro {
tocSink.close();
}
- IndexEntry index = tocSink.getRootEntry();
+ writeTocForIndexEntry(sink,
getAttributesFromMap(request.getParameters()), tocSink.getRootEntry());
+ }
+
+ void writeTocForIndexEntry(Sink sink, SinkEventAttributes listAttributes,
IndexEntry rootEntry) {
+ IndexEntry index = rootEntry;
if (index.getChildEntries().size() > 0) {
- sink.list(getAttributesFromMap(request.getParameters()));
+ sink.list(listAttributes);
int i = 1;
@@ -131,12 +136,14 @@ public class TocMacro extends AbstractMacro {
}
/**
+ * This recursive method just skips index entries that are not sections
(but still evaluates their children).
* @param sink The sink to write to.
* @param sectionIndex The section index.
* @param n The toc depth.
*/
private void writeSubSectionN(Sink sink, IndexEntry sectionIndex, int n) {
- if (fromDepth <= n) {
+ boolean isRelevantIndex = isRelevantIndexEntry(sectionIndex);
+ if (fromDepth <= n && isRelevantIndex) {
sink.listItem();
sink.link("#" + DoxiaUtils.encodeId(sectionIndex.getId()));
sink.text(sectionIndex.getTitle());
@@ -145,12 +152,12 @@ public class TocMacro extends AbstractMacro {
if (toDepth > n) {
if (sectionIndex.getChildEntries().size() > 0) {
- if (fromDepth <= n) {
+ if (fromDepth <= n && isRelevantIndex) {
sink.list();
}
for (IndexEntry subsectionIndex :
sectionIndex.getChildEntries()) {
- if (n == toDepth - 1) {
+ if (n == toDepth - 1 && isRelevantIndex) {
sink.listItem();
sink.link("#" +
DoxiaUtils.encodeId(subsectionIndex.getId()));
sink.text(subsectionIndex.getTitle());
@@ -161,17 +168,21 @@ public class TocMacro extends AbstractMacro {
}
}
- if (fromDepth <= n) {
+ if (fromDepth <= n && isRelevantIndex) {
sink.list_();
}
}
}
- if (fromDepth <= n) {
+ if (fromDepth <= n && isRelevantIndex) {
sink.listItem_();
}
}
+ static boolean isRelevantIndexEntry(IndexEntry indexEntry) {
+ return indexEntry.hasId() && indexEntry.getType().isSection();
+ }
+
/**
* @param request The MacroRequest.
* @param parameter The parameter.
diff --git
a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/CreateAnchorsForIndexEntries.java
b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/CreateAnchorsForIndexEntries.java
index 9998b99d..cb4a419d 100644
---
a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/CreateAnchorsForIndexEntries.java
+++
b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/CreateAnchorsForIndexEntries.java
@@ -36,7 +36,7 @@ public class CreateAnchorsForIndexEntries extends
IndexingSink {
@Override
protected void onIndexEntry(IndexEntry entry) {
- if (!entry.hasAnchor()) {
+ if (!entry.hasAnchor() && entry.hasId()) {
getWrappedSink().anchor(entry.getId());
getWrappedSink().anchor_();
}
diff --git
a/doxia-core/src/test/java/org/apache/maven/doxia/index/IndexingSinkTest.java
b/doxia-core/src/test/java/org/apache/maven/doxia/index/IndexingSinkTest.java
index 6267c8dd..fbd63e34 100644
---
a/doxia-core/src/test/java/org/apache/maven/doxia/index/IndexingSinkTest.java
+++
b/doxia-core/src/test/java/org/apache/maven/doxia/index/IndexingSinkTest.java
@@ -18,11 +18,15 @@
*/
package org.apache.maven.doxia.index;
+import org.apache.maven.doxia.parser.AbstractParserTest;
+import org.apache.maven.doxia.sink.impl.SinkEventTestingSink;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
class IndexingSinkTest {
+
@Test
void testGetUniqueId() {
IndexingSink sink = new IndexingSink(new IndexEntry("root"));
@@ -30,4 +34,55 @@ class IndexingSinkTest {
assertEquals("root_2", sink.getUniqueId("root"));
assertEquals("newid", sink.getUniqueId("newid"));
}
+
+ @Test
+ void testIndexingSinkWithComplexSink() {
+ SinkEventTestingSink resultSink = new SinkEventTestingSink();
+ IndexingSink sink = new IndexingSink(resultSink);
+ sink.section1();
+ sink.sectionTitle1();
+ sink.text("title1");
+ sink.sectionTitle1_();
+ sink.section2();
+ sink.section3();
+ sink.sectionTitle3();
+ sink.text("title3");
+ sink.sectionTitle3_();
+ sink.section3_();
+ sink.section2_();
+ sink.section1_();
+ sink.close();
+
+ // make sure that all events are emitted downstream
+ AbstractParserTest.assertSinkEquals(
+ resultSink.getEventList().iterator(),
+ "section1",
+ "sectionTitle1",
+ "text",
+ "sectionTitle1_",
+ "section2",
+ "section3",
+ "sectionTitle3",
+ "text",
+ "sectionTitle3_",
+ "section3_",
+ "section2_",
+ "section1_",
+ "close");
+
+ // evaluate captured index data
+ IndexEntry entry = sink.getRootEntry();
+ assertIndexEntry("index", null, 1, entry);
+ assertIndexEntry("title1", "title1", 1, entry.getFirstEntry());
+ assertIndexEntry(null, null, 1, entry.getFirstEntry().getFirstEntry());
+ assertIndexEntry(
+ "title3", "title3", 0,
entry.getFirstEntry().getFirstEntry().getFirstEntry());
+ }
+
+ private void assertIndexEntry(String id, String title, int numChildren,
IndexEntry entry) {
+ assertNotNull(entry);
+ assertEquals(id, entry.getId());
+ assertEquals(title, entry.getTitle());
+ assertEquals(numChildren, entry.getChildEntries().size());
+ }
}
diff --git
a/doxia-core/src/test/java/org/apache/maven/doxia/macro/toc/TocMacroTest.java
b/doxia-core/src/test/java/org/apache/maven/doxia/macro/toc/TocMacroTest.java
index ace3a0eb..52a82fb2 100644
---
a/doxia-core/src/test/java/org/apache/maven/doxia/macro/toc/TocMacroTest.java
+++
b/doxia-core/src/test/java/org/apache/maven/doxia/macro/toc/TocMacroTest.java
@@ -24,6 +24,8 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import org.apache.maven.doxia.index.IndexEntry;
+import org.apache.maven.doxia.index.IndexEntry.Type;
import org.apache.maven.doxia.macro.MacroExecutionException;
import org.apache.maven.doxia.macro.MacroRequest;
import org.apache.maven.doxia.markup.Markup;
@@ -199,4 +201,39 @@ public class TocMacroTest {
"<section><a id=\"" + actualLinkTarget.substring(1) +
"\"></a>" + Markup.EOL + "<h1>1 Headline</h1>",
out.toString());
}
+
+ @Test
+ void testWriteTocWithEmptyAndNotApplicableIndexEntries() {
+ TocMacro macro = new TocMacro();
+ SinkEventTestingSink sink = new SinkEventTestingSink();
+ final SinkEventAttributeSet atts = new SinkEventAttributeSet();
+ IndexEntry rootEntry = new IndexEntry("root");
+ new IndexEntry(rootEntry, null, Type.SECTION_1);
+ // toc item on level 1
+ IndexEntry entry = new IndexEntry(rootEntry, "id2", Type.SECTION_1);
+ entry.setTitle("title 1");
+ // toc item on level 2 (below toc item 1)
+ new IndexEntry(entry, "id2-1", Type.SECTION_2).setTitle("title 1 -
subtitle1");
+ // item not relevant for toc (should be skipped)
+ entry = new IndexEntry(rootEntry, "id3", Type.FIGURE);
+ // toc item below skipped item
+ new IndexEntry(entry, "id3-1", Type.SECTION_1).setTitle("title 4");
+ macro.writeTocForIndexEntry(sink, atts, rootEntry);
+
+ Iterator<SinkEventElement> it = sink.getEventList().iterator();
+ AbstractParserTest.assertSinkStartsWith(it, "list");
+ assertListItem(it, "#id2", "title 1");
+ AbstractParserTest.assertSinkStartsWith(it, "list");
+ assertListItem(it, "#id2-1", "title 1 - subtitle1");
+ AbstractParserTest.assertSinkStartsWith(it, "listItem_", "list_",
"listItem_");
+ assertListItem(it, "#id3-1", "title 4");
+ AbstractParserTest.assertSinkEquals(it, "listItem_", "list_");
+ }
+
+ void assertListItem(Iterator<SinkEventElement> it, String link, String
text) {
+ AbstractParserTest.assertSinkStartsWith(it, "listItem");
+ AbstractParserTest.assertSinkEquals(it.next(), "link", link, null);
+ AbstractParserTest.assertSinkEquals(it.next(), "text", text, null);
+ AbstractParserTest.assertSinkStartsWith(it, "link_");
+ }
}
diff --git
a/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownParserTest.java
b/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownParserTest.java
index 4308f051..3bffb066 100644
---
a/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownParserTest.java
+++
b/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownParserTest.java
@@ -686,12 +686,7 @@ public class MarkdownParserTest extends AbstractParserTest
{
"head_",
"body",
"list", // TOC start
- "listItem",
- "link",
- "text",
- "link_", // emtpy section 2 TOC entry
- "list", // sections 3 list start
- "listItem",
+ "listItem", // skip emtpy section 2 TOC entry, sections 3 list
start
"link",
"text",
"link_",
@@ -701,8 +696,6 @@ public class MarkdownParserTest extends AbstractParserTest {
"text",
"link_",
"listItem_", // second section 3 TOC entry
- "list_", // sections 3 list end
- "listItem_", // emtpy section 2 TOC entry end
"list_", // TOC end
"text",
"section1",
@@ -801,6 +794,46 @@ public class MarkdownParserTest extends AbstractParserTest
{
content.toString());
}
+ /** Checks that non consecutive headings are normalized according to Sink
API restrictions. */
+ @Test
+ public void testNonConsecutiveHeadingSections() throws ParseException,
IOException {
+ parser.setEmitAnchorsForIndexableEntries(true);
+ List<SinkEventElement> eventList =
+ parseFileToEventTestingSink("headings").getEventList();
+
+ assertSinkEquals(
+ eventList.iterator(),
+ "head",
+ "title",
+ "text",
+ "title_",
+ "head_",
+ "body",
+ "section1",
+ "anchor",
+ "anchor_",
+ "sectionTitle1",
+ "text",
+ "sectionTitle1_",
+ "paragraph",
+ "text",
+ "paragraph_",
+ "section2",
+ "section3",
+ "anchor",
+ "anchor_",
+ "sectionTitle3",
+ "text",
+ "sectionTitle3_",
+ "paragraph",
+ "text",
+ "paragraph_",
+ "section3_",
+ "section2_",
+ "section1_",
+ "body_");
+ }
+
@Override
protected void assertEventPrefix(Iterator<SinkEventElement> eventIterator)
{
assertSinkStartsWith(eventIterator, "head", "head_", "body");
diff --git a/doxia-modules/doxia-module-markdown/src/test/resources/headings.md
b/doxia-modules/doxia-module-markdown/src/test/resources/headings.md
new file mode 100644
index 00000000..ecd156c3
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/test/resources/headings.md
@@ -0,0 +1,7 @@
+# Heading 1
+
+Text
+
+### Heading 3
+
+Text 2
\ No newline at end of file