This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch feature/leave-links-optionally-untouched
in repository https://gitbox.apache.org/repos/asf/maven-doxia.git

commit 10a8b48870f7f36f50734515e9eb7662d1bd2d84
Author: Konrad Windszus <[email protected]>
AuthorDate: Wed Feb 11 11:08:52 2026 +0100

    Optionally leave fragments of internal links untouched
    
    Evaluate "rel" attribute on "a" elements. If set to "external" the
    fragment is not
    normalized to a Doxia ID.
    
    This is particularly helpful to link out to (internal) javadoc, where
    fragments contain "(" and ")" characters which are invalid in Doxia IDs.
    
    This closes #1029
---
 .../maven/doxia/parser/Xhtml5BaseParser.java       |  4 ++-
 .../maven/doxia/parser/Xhtml5BaseParserTest.java   | 41 +++++++++++++++-------
 .../doxia-module-markdown/src/site/apt/index.apt   |  3 +-
 .../doxia/module/markdown/MarkdownParserTest.java  | 27 ++++++++++++++
 .../doxia-module-xhtml5/src/site/markdown/index.md | 33 +++++++++++++++++
 5 files changed, 94 insertions(+), 14 deletions(-)

diff --git 
a/doxia-core/src/main/java/org/apache/maven/doxia/parser/Xhtml5BaseParser.java 
b/doxia-core/src/main/java/org/apache/maven/doxia/parser/Xhtml5BaseParser.java
index dbfed392..a2b03c81 100644
--- 
a/doxia-core/src/main/java/org/apache/maven/doxia/parser/Xhtml5BaseParser.java
+++ 
b/doxia-core/src/main/java/org/apache/maven/doxia/parser/Xhtml5BaseParser.java
@@ -813,7 +813,9 @@ public class Xhtml5BaseParser extends AbstractXmlParser 
implements HtmlMarkup {
 
         if (href != null) {
             int hashIndex = href.indexOf('#');
-            if (hashIndex != -1 && !DoxiaUtils.isExternalLink(href)) {
+            if (hashIndex != -1
+                    && !DoxiaUtils.isExternalLink(href)
+                    && 
!"external".equals(attribs.getAttribute(Attribute.REL.toString()))) {
                 String hash = href.substring(hashIndex + 1);
 
                 if (!DoxiaUtils.isValidId(hash)) {
diff --git 
a/doxia-core/src/test/java/org/apache/maven/doxia/parser/Xhtml5BaseParserTest.java
 
b/doxia-core/src/test/java/org/apache/maven/doxia/parser/Xhtml5BaseParserTest.java
index 1777e648..b29b8e3d 100644
--- 
a/doxia-core/src/test/java/org/apache/maven/doxia/parser/Xhtml5BaseParserTest.java
+++ 
b/doxia-core/src/test/java/org/apache/maven/doxia/parser/Xhtml5BaseParserTest.java
@@ -841,6 +841,35 @@ class Xhtml5BaseParserTest extends AbstractParserTest {
         assertEquals("division_", element.getName());
     }
 
+    @Test
+    void anchorLinkWithExternalRel() throws Exception {
+        // although the fragment is not a valid doxia id it should be used as 
is, because the rel="external" indicates
+        // that this is an external link
+        String text = "<a href=\"index.html#1invalid\" rel=\"external\"></a>";
+
+        parser.parse(text, sink);
+        Iterator<SinkEventElement> it = sink.getEventList().iterator();
+
+        SinkEventElement element = it.next();
+
+        assertEquals("link", element.getName());
+        assertEquals("index.html#1invalid", element.getArgs()[0]);
+        assertEquals("external", ((SinkEventAttributeSet) 
element.getArgs()[1]).getAttribute("rel"));
+        assertEquals("link_", it.next().getName());
+    }
+
+    @Test
+    void anchorWithName() throws ParseException {
+        String text = "<a name=\"test\"></a>";
+
+        parser.parse(text, sink);
+
+        Iterator<SinkEventElement> it = sink.getEventList().iterator();
+        // first attribute is the id, second is all given attributes
+        assertSinkEquals(it.next(), "anchor", "test", new 
SinkEventAttributeSet("name", "test"));
+        assertSinkEquals(it, "anchor_");
+    }
+
     /**
      * Test entities in attributes.
      *
@@ -942,16 +971,4 @@ class Xhtml5BaseParserTest extends AbstractParserTest {
     protected String getVerbatimCodeSource() {
         return "<pre><code>&lt;&gt;{}=#*</code></pre>";
     }
-
-    @Test
-    void anchorWithName() throws ParseException {
-        String text = "<a name=\"test\"></a>";
-
-        parser.parse(text, sink);
-
-        Iterator<SinkEventElement> it = sink.getEventList().iterator();
-        // first attribute is the id, second is all given attributes
-        assertSinkEquals(it.next(), "anchor", "test", new 
SinkEventAttributeSet("name", "test"));
-        assertSinkEquals(it, "anchor_");
-    }
 }
diff --git a/doxia-modules/doxia-module-markdown/src/site/apt/index.apt 
b/doxia-modules/doxia-module-markdown/src/site/apt/index.apt
index bbfbb15c..55aad201 100644
--- a/doxia-modules/doxia-module-markdown/src/site/apt/index.apt
+++ b/doxia-modules/doxia-module-markdown/src/site/apt/index.apt
@@ -82,7 +82,8 @@ doxia-module-markdown
   
 * Parser
 
-  The parser will first convert Markdown into HTML and then parse the HTML 
into Doxia Sink API methods calls.
+  The parser will first convert Markdown into HTML and then parse the HTML 
into Doxia Sink API methods calls leveraging the 
+  {{{../doxia-module-xhtml5/index.html}XHTML5 parser}}.
 
 * References
 
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 1fad1a69..27a29f72 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
@@ -888,4 +888,31 @@ class MarkdownParserTest extends AbstractParserTest {
         assertSinkStartsWith(eventIterator, "paragraph", "anchor", "text", 
"anchor_", "paragraph_");
         assertEventSuffix(eventIterator);
     }
+
+    @Test
+    void relativeLinkWithAnchorInvalidDoxiaId() throws ParseException {
+        Iterator<SinkEventElement> eventIterator = 
parseSourceToEventTestingSink("[Test URL](test.html#anchor\\(\\))")
+                .getEventList()
+                .iterator();
+        assertEventPrefix(eventIterator);
+        assertSinkStartsWith(eventIterator, "paragraph");
+        SinkEventElement linkEvent = eventIterator.next();
+        assertEquals("link", linkEvent.getName());
+        // converted to valid doxia id (through DoxiaUtils.encodeId)
+        assertEquals("test.html#anchor.28.29", linkEvent.getArgs()[0]);
+    }
+
+    @Test
+    void relativeLinkWithAnchorInvalidDoxiaIdAndRelExternal() throws 
ParseException {
+        Iterator<SinkEventElement> eventIterator = 
parseSourceToEventTestingSink(
+                        "<a href=\"test.html#anchor()\" rel=\"external\">Test 
URL</a>")
+                .getEventList()
+                .iterator();
+        assertEventPrefix(eventIterator);
+        assertSinkStartsWith(eventIterator, "paragraph");
+        SinkEventElement linkEvent = eventIterator.next();
+        assertEquals("link", linkEvent.getName());
+        // converted to valid doxia id (through DoxiaUtils.encodeId)
+        assertEquals("test.html#anchor()", linkEvent.getArgs()[0]);
+    }
 }
diff --git a/doxia-modules/doxia-module-xhtml5/src/site/markdown/index.md 
b/doxia-modules/doxia-module-xhtml5/src/site/markdown/index.md
new file mode 100644
index 00000000..f2ef3f70
--- /dev/null
+++ b/doxia-modules/doxia-module-xhtml5/src/site/markdown/index.md
@@ -0,0 +1,33 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+# Doxia XHTML5 Module
+
+This parser and sink digests and emits sources compliant with the [XML syntax 
of HTML5](https://html.spec.whatwg.org/#the-xhtml-syntax).
+
+## Special handling of anchors
+
+Anchor ids/names need to follow a strict syntax in Doxia, therefore both 
anchor links as well as targets are automatically adjusted to comply with that 
syntax. Further details in 
[DoxiaUtils.encodeId(...)](../../doxia-core/apidocs/org/apache/maven/doxia/util/DoxiaUtils.html#encodeId-java.lang.String-).
+
+In order to leave the links unprocessed use attribute `rel` with value 
`external` like this
+
+```
+<a href="testpage#specialanchor()" rel="external">...</a>
+```
+
+Otherwise the fragment `specialanchor()` would be converted as `(` and `)` are 
not valid in Doxia IDs.
\ No newline at end of file

Reply via email to