This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 870029043b6b camel-docling: return DocTags content and reject
html_split_page on docling-serve (#29594)
870029043b6b is described below
commit 870029043b6b55d4117857f4f21c35568bdd2be4
Author: WMK666666 <[email protected]>
AuthorDate: Fri Nov 13 22:54:07 2026 +0800
camel-docling: return DocTags content and reject html_split_page on
docling-serve (#29594)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../component/docling/DoclingContentExtractor.java | 5 ++
.../camel/component/docling/DoclingProducer.java | 17 ++++-
.../docling/DoclingContentExtractorTest.java | 81 ++++++++++++++++++++++
.../docling/DoclingServeOutputFormatTest.java | 80 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 7 ++
5 files changed, 188 insertions(+), 2 deletions(-)
diff --git
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingContentExtractor.java
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingContentExtractor.java
index d2766a96216d..e1f56f290bce 100644
---
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingContentExtractor.java
+++
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingContentExtractor.java
@@ -61,6 +61,9 @@ final class DoclingContentExtractor {
case "text":
String text = document.getTextContent();
return text != null ? text : "";
+ case "doctags":
+ String doctags = document.getDoctagsContent();
+ return doctags != null ? doctags : "";
case "json":
// Return the document JSON content
var jsonDoc = document.getJsonContent();
@@ -98,6 +101,8 @@ final class DoclingContentExtractor {
case "text":
case "txt":
return "text";
+ case "doctags":
+ return "doctags";
default:
return "md";
}
diff --git
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java
index 430581c93030..206d5b3dfa61 100644
---
a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java
+++
b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java
@@ -1726,7 +1726,16 @@ public class DoclingProducer extends DefaultProducer {
return DoclingContentExtractor.extract(response, outputFormat);
}
- private OutputFormat mapToOutputFormat(String outputFormat) {
+ /**
+ * Maps the {@code outputFormat} of the exchange to the format asked of
docling-serve.
+ * <p>
+ * docling-serve fills only the content fields of the formats it was asked
for, and it has no field for the split
+ * pages: {@code DocumentResponse} carries {@code doctagsContent}, {@code
htmlContent}, {@code jsonContent},
+ * {@code markdownContent} and {@code textContent}, and the html one is
filled only when HTML is requested. Asking
+ * it for {@code html_split_page} therefore returns a response whose
content fields are all null, and the exchange
+ * body comes out empty, so it fails with the reason instead.
+ */
+ OutputFormat mapToOutputFormat(String outputFormat) {
if (outputFormat == null) {
return OutputFormat.MARKDOWN;
}
@@ -1745,7 +1754,11 @@ public class DoclingProducer extends DefaultProducer {
case "doctags":
return OutputFormat.DOCTAGS;
case "html_split_page":
- return OutputFormat.HTML_SPLIT_PAGE;
+ throw new IllegalArgumentException(
+ "outputFormat " + outputFormat + " is not supported:
docling-serve does not return the split"
+ + " page HTML in the
response body, so the exchange body would be"
+ + " empty. Use
outputFormat=html to get the HTML of the whole"
+ + " document");
default:
return OutputFormat.MARKDOWN;
}
diff --git
a/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingContentExtractorTest.java
b/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingContentExtractorTest.java
new file mode 100644
index 000000000000..c0a10b56d9a3
--- /dev/null
+++
b/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingContentExtractorTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+package org.apache.camel.component.docling;
+
+import ai.docling.serve.api.convert.response.ConvertDocumentResponse;
+import ai.docling.serve.api.convert.response.DocumentResponse;
+import ai.docling.serve.api.convert.response.InBodyConvertDocumentResponse;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests that {@link DoclingContentExtractor} returns the content matching the
requested output format.
+ * <p>
+ * {@code doctags} is one of the documented values of the {@code outputFormat}
option, and docling-serve returns it in a
+ * field of its own, so asking for it must not fall through to the markdown
branch: that branch finds no markdown in a
+ * response that only carries DocTags and returns an empty body.
+ */
+class DoclingContentExtractorTest {
+
+ @Test
+ void doctagsReturnsTheDoctagsContent() throws Exception {
+ // docling-serve only fills the fields of the formats that were
requested, so a request for doctags returns a
+ // document with doctags content and no markdown at all
+ ConvertDocumentResponse response =
responseWith(document().doctagsContent("<doctag>text</doctag>").build());
+
+ assertThat(DoclingContentExtractor.extract(response,
"doctags")).isEqualTo("<doctag>text</doctag>");
+ }
+
+ @Test
+ void doctagsIsRecognisedInUpperCase() throws Exception {
+ ConvertDocumentResponse response =
responseWith(document().doctagsContent("<doctag>text</doctag>").build());
+
+ assertThat(DoclingContentExtractor.extract(response,
"DOCTAGS")).isEqualTo("<doctag>text</doctag>");
+ }
+
+ @Test
+ void missingDoctagsContentIsAnEmptyBody() throws Exception {
+ ConvertDocumentResponse response =
responseWith(document().markdownContent("# text").build());
+
+ assertThat(DoclingContentExtractor.extract(response,
"doctags")).isEmpty();
+ }
+
+ @Test
+ void otherFormatsStillReturnTheirOwnContent() throws Exception {
+ // guard: widening the doctags case must not redirect the formats that
were already right
+ ConvertDocumentResponse everything = responseWith(document()
+ .markdownContent("# md")
+ .htmlContent("<h1>html</h1>")
+ .textContent("plain")
+ .doctagsContent("<doctag>text</doctag>")
+ .build());
+
+ assertThat(DoclingContentExtractor.extract(everything,
"markdown")).isEqualTo("# md");
+ assertThat(DoclingContentExtractor.extract(everything,
"html")).isEqualTo("<h1>html</h1>");
+ assertThat(DoclingContentExtractor.extract(everything,
"text")).isEqualTo("plain");
+ assertThat(DoclingContentExtractor.extract(everything,
"txt")).isEqualTo("plain");
+ }
+
+ private static DocumentResponse.Builder document() {
+ return DocumentResponse.builder().filename("input.pdf");
+ }
+
+ private static ConvertDocumentResponse responseWith(DocumentResponse
document) {
+ return
InBodyConvertDocumentResponse.builder().document(document).status("success").build();
+ }
+}
diff --git
a/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingServeOutputFormatTest.java
b/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingServeOutputFormatTest.java
new file mode 100644
index 000000000000..f2a6ab298085
--- /dev/null
+++
b/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingServeOutputFormatTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+package org.apache.camel.component.docling;
+
+import ai.docling.serve.api.convert.request.options.OutputFormat;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests the format the docling-serve request asks for, for each documented
value of the {@code outputFormat} option.
+ * <p>
+ * docling-serve fills only the content fields of the formats it was asked
for, and it has no field for the split pages:
+ * {@code html_split_page} was asked of the service, the response carried no
content at all, and the body came out
+ * empty. It now fails with the reason instead, and the five values the
service can return still map to the format that
+ * fills their field.
+ */
+class DoclingServeOutputFormatTest extends CamelTestSupport {
+
+ @Test
+ void htmlSplitPageFailsWithTheReasonInsteadOfAnEmptyBody() {
+ assertThatThrownBy(() -> toFormat("html_split_page"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("html_split_page")
+ .hasMessageContaining("docling-serve")
+ .hasMessageContaining("outputFormat=html");
+ }
+
+ @Test
+ void htmlSplitPageIsRejectedInUpperCaseToo() {
+ assertThatThrownBy(() -> toFormat("HTML_SPLIT_PAGE"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void theFormatsDoclingServeCanReturnAreStillRequested() throws Exception {
+ assertThat(toFormat("markdown")).isEqualTo(OutputFormat.MARKDOWN);
+ assertThat(toFormat("md")).isEqualTo(OutputFormat.MARKDOWN);
+ assertThat(toFormat("html")).isEqualTo(OutputFormat.HTML);
+ assertThat(toFormat("json")).isEqualTo(OutputFormat.JSON);
+ assertThat(toFormat("text")).isEqualTo(OutputFormat.TEXT);
+ assertThat(toFormat("txt")).isEqualTo(OutputFormat.TEXT);
+ assertThat(toFormat("doctags")).isEqualTo(OutputFormat.DOCTAGS);
+ }
+
+ private OutputFormat toFormat(String outputFormat) throws Exception {
+ DoclingEndpoint endpoint
+ =
context.getEndpoint("docling:convert?operation=CONVERT_TO_MARKDOWN",
DoclingEndpoint.class);
+ DoclingProducer producer = (DoclingProducer) endpoint.createProducer();
+
+ return producer.mapToOutputFormat(outputFormat);
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+ from("direct:noop").to("mock:noop");
+ }
+ };
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 6e3583d77b8d..d664c93c3d5f 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -8553,3 +8553,10 @@ A Vert.x that completes after the timeout is closed,
rather than being left join
`timeout` must now be a positive number of seconds on that path: `timeout=0`
used to mean "do not wait", which left the
component without a Vert.x, and is rejected with a clear message instead.
+
+=== camel-docling - html_split_page is rejected by docling-serve
+
+`outputFormat=html_split_page` produced an empty body on the docling-serve
path: the request asked docling-serve for
+the split pages, whose content it does not return in the response, so every
content field of the reply was empty. It
+now fails with an `IllegalArgumentException` that names the reason and the way
out, `outputFormat=html` to get the
+HTML of the whole document.