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.

Reply via email to