diff --git a/commons/doc-maven-plugin/src/main/java/org/openidentityplatform/doc/maven/AntoraMojo.java b/commons/doc-maven-plugin/src/main/java/org/openidentityplatform/doc/maven/AntoraMojo.java index c12a48f09..56d054a6d 100644 --- a/commons/doc-maven-plugin/src/main/java/org/openidentityplatform/doc/maven/AntoraMojo.java +++ b/commons/doc-maven-plugin/src/main/java/org/openidentityplatform/doc/maven/AntoraMojo.java @@ -12,6 +12,7 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2024 3A Systems LLC. + * Portions Copyright 2026 3A Systems, LLC. */ package org.openidentityplatform.doc.maven; @@ -30,7 +31,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -89,6 +92,7 @@ private void convertPartialsForAntora(Path productPath, File docDir) throws IOEx File[] docFiles = docDir.listFiles(); for (File docFile : docFiles) { String adoc = FileUtils.readFileToString(docFile, StandardCharsets.UTF_8); + warnLegacyLinks(docFile, adoc); adoc = convertForAntora(adoc); Path convertedFilePath = Paths.get(partialsPath.toString(), docFile.getName()); @@ -126,23 +130,24 @@ private void convertDocForAntora(Path productPath, File docDir) throws IOExcepti FileUtils.writeStringToFile(navFilePath.toFile(), nav, StandardCharsets.UTF_8); } else { Path convertedFilePath = Paths.get(docModulePagesPath.toString(), docFile.getName()); + warnLegacyLinks(docFile, adoc); adoc = convertForAntora(adoc); FileUtils.writeStringToFile(convertedFilePath.toFile(), adoc, StandardCharsets.UTF_8); } } } - private String convertForAntora(String adoc) { + static String convertForAntora(String adoc) { adoc = adoc.replace("image::images/", "image::ROOT:"); adoc = adoc.replace("image:images/", "image:ROOT:"); adoc = adoc.replace("include::../partials/", "include::ROOT:partial$"); adoc = adoc.replace("link:../attachments/", "xref:ROOT:attachment$"); - adoc = adoc.replace(":table-caption!:", ":table-caption!:\n:leveloffset: -1\""); + adoc = adoc.replace(":table-caption!:", ":table-caption!:\n:leveloffset: -1"); adoc = convertXrefsToAntora(adoc); return adoc; } - private String convertXrefsToAntora(String adoc) { + static String convertXrefsToAntora(String adoc) { Pattern p = Pattern.compile("xref\\:(.+?)\\["); Matcher m = p.matcher(adoc); @@ -152,6 +157,7 @@ private String convertXrefsToAntora(String adoc) { builder.append(adoc, i, m.start()); String url = m.group(1); url = url.replace("../", ""); + url = url.replaceAll("(^|/)(\\./)+", "$1"); url = url.replace("/", ":"); builder.append("xref:").append(url).append("["); @@ -162,6 +168,28 @@ private String convertXrefsToAntora(String adoc) { return builder.toString(); } + private static final Pattern LEGACY_LINK_PATTERN = Pattern.compile("link:\\.\\./\\.\\./\\.\\./[^\\[\\s]*"); + + /** + * Finds ForgeRock-era cross-guide links such as {@code link:../../../openam/13/admin-guide/}. + * They are not converted and resolve to 404 on the Antora site. + */ + static List findLegacyLinks(String adoc) { + List links = new ArrayList<>(); + Matcher m = LEGACY_LINK_PATTERN.matcher(adoc); + while (m.find()) { + links.add(m.group()); + } + return links; + } + + void warnLegacyLinks(File docFile, String adoc) { + for (String link : findLegacyLinks(adoc)) { + getLog().warn("Legacy cross-guide link is not converted and will be broken in Antora: " + + docFile + ": " + link); + } + } + private void createIndexForRoot() throws IOException, MojoExecutionException { Path rootPath = getRootModulePath(); Path pagesPath = Paths.get(rootPath.toString(), "pages"); diff --git a/commons/doc-maven-plugin/src/test/java/org/openidentityplatform/doc/maven/AntoraMojoTest.java b/commons/doc-maven-plugin/src/test/java/org/openidentityplatform/doc/maven/AntoraMojoTest.java index 2acaa071c..fd4f2644d 100644 --- a/commons/doc-maven-plugin/src/test/java/org/openidentityplatform/doc/maven/AntoraMojoTest.java +++ b/commons/doc-maven-plugin/src/test/java/org/openidentityplatform/doc/maven/AntoraMojoTest.java @@ -12,16 +12,20 @@ * information: "Portions copyright [year] [name of copyright owner]". * * Copyright 2024 3A Systems LLC. + * Portions Copyright 2026 3A Systems, LLC. */ package org.openidentityplatform.doc.maven; +import org.apache.maven.plugin.logging.SystemStreamLog; import org.apache.maven.plugin.testing.AbstractMojoTestCase; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.File; +import java.util.ArrayList; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -47,4 +51,62 @@ public void testExecute() throws Exception { this.configureMojo(antoraMojo, "doc-maven-plugin", pom); this.getVariablesAndValuesFromObject(antoraMojo); } -} \ No newline at end of file + + @Test + public void testConvertXrefWithCurrentDirPrefix() { + assertThat(AntoraMojo.convertXrefsToAntora( + "see xref:./chap-jee-agent-config.adoc#configure-j2ee-policy-agent[Configure]")) + .isEqualTo("see xref:chap-jee-agent-config.adoc#configure-j2ee-policy-agent[Configure]"); + } + + @Test + public void testConvertXrefWithRepeatedCurrentDirSegments() { + assertThat(AntoraMojo.convertXrefsToAntora("xref:./././a.adoc[A]")) + .isEqualTo("xref:a.adoc[A]"); + assertThat(AntoraMojo.convertXrefsToAntora("xref:a/././b.adoc[B]")) + .isEqualTo("xref:a:b.adoc[B]"); + assertThat(AntoraMojo.convertXrefsToAntora("xref:../mod/././p.adoc[P]")) + .isEqualTo("xref:mod:p.adoc[P]"); + } + + @Test + public void testConvertXrefToOtherModule() { + assertThat(AntoraMojo.convertXrefsToAntora("xref:../reference/./ch02.adoc#anchor[Ref]")) + .isEqualTo("xref:reference:ch02.adoc#anchor[Ref]"); + assertThat(AntoraMojo.convertXrefsToAntora("xref:ch02.adoc[Ref]")) + .isEqualTo("xref:ch02.adoc[Ref]"); + } + + @Test + public void testLeveloffsetHasNoStrayQuote() { + assertThat(AntoraMojo.convertForAntora(":table-caption!:\n")) + .isEqualTo(":table-caption!:\n:leveloffset: -1\n"); + } + + @Test + public void testFindLegacyLinks() { + assertThat(AntoraMojo.findLegacyLinks( + "link:../../../openam/13/admin-guide/#chap-cdsso[CDSSO] and " + + "link:../attachments/file.zip[file] and " + + "link:../../../opendj/3.5/admin-guide/[OpenDJ]")) + .containsExactly("link:../../../openam/13/admin-guide/#chap-cdsso", + "link:../../../opendj/3.5/admin-guide/"); + } + + @Test + public void testWarnLegacyLinksNamesFileAndLink() { + AntoraMojo mojo = new AntoraMojo(); + List warnings = new ArrayList<>(); + mojo.setLog(new SystemStreamLog() { + @Override + public void warn(CharSequence content) { + warnings.add(content.toString()); + } + }); + mojo.warnLegacyLinks(new File("chap-cdsso.adoc"), + "link:../../../openam/13/admin-guide/#chap-cdsso[CDSSO]"); + assertThat(warnings).hasSize(1); + assertThat(warnings.get(0)) + .contains("chap-cdsso.adoc", "link:../../../openam/13/admin-guide/#chap-cdsso"); + } +}