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

Reply via email to