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><>{}=#*</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
