From 27334b2b37a2743cb024ac5e71c799d044cc6a43 Mon Sep 17 00:00:00 2001 From: wantaek Date: Mon, 17 Aug 2026 01:53:51 +0900 Subject: [PATCH] Fix `JsoupDocumentReader` dropping relative link URLs `JsoupDocumentReader` parses with an empty base URI, so jsoup cannot resolve a relative `href` and `abs:href` yields an empty string. With `includeLinkUrls` enabled, every relative link lands in the `linkUrls` metadata as "". The reference documentation states that the base URI for resolving relative links is taken from URL resources, and the module README promises absolute URLs for every link in the document. Take the base URI from the resource, keeping the empty one for resources that do not resolve to a URL. Signed-off-by: wantaek --- .../ai/reader/jsoup/JsoupDocumentReader.java | 18 +++++++++++++++++- .../reader/jsoup/JsoupDocumentReaderTests.java | 15 +++++++++++++++ .../test/resources/test-relative-links.html | 10 ++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 document-readers/spring-ai-jsoup-document-reader/src/test/resources/test-relative-links.html diff --git a/document-readers/spring-ai-jsoup-document-reader/src/main/java/org/springframework/ai/reader/jsoup/JsoupDocumentReader.java b/document-readers/spring-ai-jsoup-document-reader/src/main/java/org/springframework/ai/reader/jsoup/JsoupDocumentReader.java index 58d6c264c1..4135fe5622 100644 --- a/document-readers/spring-ai-jsoup-document-reader/src/main/java/org/springframework/ai/reader/jsoup/JsoupDocumentReader.java +++ b/document-readers/spring-ai-jsoup-document-reader/src/main/java/org/springframework/ai/reader/jsoup/JsoupDocumentReader.java @@ -69,7 +69,7 @@ public JsoupDocumentReader(Resource htmlResource, JsoupDocumentReaderConfig conf @Override public List get() { try (InputStream inputStream = this.htmlResource.getInputStream()) { - org.jsoup.nodes.Document doc = Jsoup.parse(inputStream, this.config.charset, ""); + org.jsoup.nodes.Document doc = Jsoup.parse(inputStream, this.config.charset, resolveBaseUri()); List documents = new ArrayList<>(); @@ -108,6 +108,22 @@ else if (this.config.groupByElement) { } } + /** + * Returns the base URI used to resolve relative links against, taken from the + * resource itself. Resources that cannot be resolved to a URL, such as a + * {@link org.springframework.core.io.ByteArrayResource}, have no base to resolve + * against and keep an empty one. + */ + private String resolveBaseUri() { + try { + return this.htmlResource.getURL().toString(); + } + catch (IOException ignored) { + // A resource without a URL has no base to resolve against. + return ""; + } + } + private void addMetadata(org.jsoup.nodes.Document jsoupDoc, Document springDoc) { Map metadata = new HashMap<>(); metadata.put("title", jsoupDoc.title()); diff --git a/document-readers/spring-ai-jsoup-document-reader/src/test/java/org/springframework/ai/reader/jsoup/JsoupDocumentReaderTests.java b/document-readers/spring-ai-jsoup-document-reader/src/test/java/org/springframework/ai/reader/jsoup/JsoupDocumentReaderTests.java index 7dd25f92e9..723a805433 100644 --- a/document-readers/spring-ai-jsoup-document-reader/src/test/java/org/springframework/ai/reader/jsoup/JsoupDocumentReaderTests.java +++ b/document-readers/spring-ai-jsoup-document-reader/src/test/java/org/springframework/ai/reader/jsoup/JsoupDocumentReaderTests.java @@ -95,6 +95,21 @@ void testWithLinkUrls() { assertThat(linkUrls).contains("https://spring.io/"); } + @Test + void testWithRelativeLinkUrls() { + JsoupDocumentReader reader = new JsoupDocumentReader( + new DefaultResourceLoader().getResource("classpath:/test-relative-links.html"), + JsoupDocumentReaderConfig.builder().includeLinkUrls(true).build()); + List documents = reader.get(); + assertThat(documents).hasSize(1); + Document document = documents.get(0); + + List linkUrls = (List) document.getMetadata().get("linkUrls"); + assertThat(linkUrls).contains("https://spring.io/"); + assertThat(linkUrls).noneMatch(String::isEmpty); + assertThat(linkUrls).anySatisfy(url -> assertThat(url).endsWith("/guide.html")); + } + @Test void testWithMetadataTags() { JsoupDocumentReader reader = new JsoupDocumentReader( diff --git a/document-readers/spring-ai-jsoup-document-reader/src/test/resources/test-relative-links.html b/document-readers/spring-ai-jsoup-document-reader/src/test/resources/test-relative-links.html new file mode 100644 index 0000000000..f4227dd6d6 --- /dev/null +++ b/document-readers/spring-ai-jsoup-document-reader/src/test/resources/test-relative-links.html @@ -0,0 +1,10 @@ + + + + Test Relative Links + + +Relative link +Absolute link + +