Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ static String extract(ConvertDocumentResponse response, String outputFormat) thr
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();
Expand Down Expand Up @@ -98,6 +101,8 @@ private static String mapOutputFormat(String outputFormat) {
case "text":
case "txt":
return "text";
case "doctags":
return "doctags";
default:
return "md";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1726,7 +1726,16 @@ private String extractConvertedContent(ConvertDocumentResponse response, String
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;
}
Expand All @@ -1745,7 +1754,11 @@ private OutputFormat mapToOutputFormat(String outputFormat) {
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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8198,3 +8198,10 @@ not pass a language on; a language header alone turns OCR on, unless the endpoin

The language itself is passed to docling untouched, in both forms: it is docling's own identifier (language codes such
as `en,de`, BCP-47 tags such as `iso:zh-Hant`, or `script/Latin`), and docling validates it.

=== 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.