From ba327f1e9af906987b1920d15eefcc7868729a2d Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 30 May 2017 12:53:16 +0100 Subject: [PATCH] Ensure that all nodes for a snippet refer to main document Previously, when the operation block macro rendered the fragment for a snippet, every top-level block in the fragment had its parent set. Setting the parent has the side-effect of also setting the block's document to be that of its parent. This meant that every top-level block's document referred to the main document, but any descendents were left with their document referring to the fragment document. This did not cause a problem with the HTML backend (which does not consider a block's document) but the PDF backend does consider a block's document resulting in the descendents being omitted from the generated PDF. This commit updates the operation block macro to set the parent of every block in the fragment to its correct value. This has the side-effect of causing the block to update its document reference to point to the main document. Closes gh-396 --- build.gradle | 4 +- spring-restdocs-asciidoctor/build.gradle | 2 + .../extensions/operation_block_macro.rb | 6 +- .../asciidoctor/OperationBlockMacroTests.java | 83 ++++++++++++++++++- .../resources/operations/all-snippets.html | 36 ++++++++ .../resources/operations/snippet-table.html | 36 ++++++++ .../some-operation/response-fields.adoc | 16 ++++ 7 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 spring-restdocs-asciidoctor/src/test/resources/operations/snippet-table.html create mode 100644 spring-restdocs-asciidoctor/src/test/resources/some-operation/response-fields.adoc diff --git a/build.gradle b/build.gradle index 86888de6..6b55d694 100644 --- a/build.gradle +++ b/build.gradle @@ -72,7 +72,9 @@ subprojects { dependency 'javax.validation:validation-api:1.1.0.Final' dependency 'junit:junit:4.12' dependency 'io.rest-assured:rest-assured:3.0.2' - dependency 'org.asciidoctor:asciidoctorj:1.5.3.1' + dependency 'org.apache.pdfbox:pdfbox:2.0.6' + dependency 'org.asciidoctor:asciidoctorj:1.5.5' + dependency 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.15' dependency 'org.hamcrest:hamcrest-core:1.3' dependency 'org.hamcrest:hamcrest-library:1.3' dependency 'org.hibernate:hibernate-validator:5.2.2.Final' diff --git a/spring-restdocs-asciidoctor/build.gradle b/spring-restdocs-asciidoctor/build.gradle index 0a696d24..51352428 100644 --- a/spring-restdocs-asciidoctor/build.gradle +++ b/spring-restdocs-asciidoctor/build.gradle @@ -3,6 +3,8 @@ description = 'Asciidoctor extensions for Spring REST Docs' dependencies { provided 'org.asciidoctor:asciidoctorj' testCompile 'junit:junit' + testCompile 'org.apache.pdfbox:pdfbox' testCompile 'org.asciidoctor:asciidoctorj' testCompile 'org.springframework:spring-core' + testRuntime 'org.asciidoctor:asciidoctorj-pdf' } \ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/main/resources/extensions/operation_block_macro.rb b/spring-restdocs-asciidoctor/src/main/resources/extensions/operation_block_macro.rb index 9ddddc2d..5fe2648a 100644 --- a/spring-restdocs-asciidoctor/src/main/resources/extensions/operation_block_macro.rb +++ b/spring-restdocs-asciidoctor/src/main/resources/extensions/operation_block_macro.rb @@ -47,13 +47,15 @@ class OperationBlockMacro < Asciidoctor::Extensions::BlockMacroProcessor def add_blocks(content, doc, parent) options = { safe: doc.options[:safe], - attributes: { 'fragment' => '', - 'projectdir' => doc.attr(:projectdir) } } + attributes: { 'projectdir' => doc.attr(:projectdir) } } fragment = Asciidoctor.load content, options fragment.blocks.each do |b| b.parent = parent parent << b end + parent.find_by.each do |b| + b.parent = b.parent unless b.is_a? Asciidoctor::Document + end end def snippets_to_include(snippet_names, snippets_dir, operation) diff --git a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/OperationBlockMacroTests.java b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/OperationBlockMacroTests.java index ab0dda63..0770165e 100644 --- a/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/OperationBlockMacroTests.java +++ b/spring-restdocs-asciidoctor/src/test/java/org/springframework/restdocs/asciidoctor/OperationBlockMacroTests.java @@ -22,7 +22,14 @@ import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import org.apache.pdfbox.contentstream.PDFStreamEngine; +import org.apache.pdfbox.contentstream.operator.Operator; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSString; +import org.apache.pdfbox.pdmodel.PDDocument; import org.asciidoctor.Asciidoctor; import org.asciidoctor.Attributes; import org.asciidoctor.Options; @@ -33,6 +40,7 @@ import org.junit.Test; import org.springframework.util.FileSystemUtils; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.assertThat; @@ -62,12 +70,38 @@ public class OperationBlockMacroTests { } @Test - public void simpleSnippetInclude() throws Exception { + public void codeBlockSnippetInclude() throws Exception { String result = this.asciidoctor.convert( "operation::some-operation[snippets='curl-request']", this.options); assertThat(result, equalTo(getExpectedContentFromFile("snippet-simple"))); } + @Test + public void codeBlockSnippetIncludeWithPdfBackend() throws Exception { + File output = configurePdfOutput(); + this.asciidoctor.convert("operation::some-operation[snippets='curl-request']", + this.options); + assertThat(extractStrings(output), + hasItems("Curl request", "$ curl 'http://localhost:8080/' -i", "1")); + } + + @Test + public void tableSnippetInclude() throws Exception { + String result = this.asciidoctor.convert( + "operation::some-operation[snippets='response-fields']", this.options); + assertThat(result, equalTo(getExpectedContentFromFile("snippet-table"))); + } + + @Test + public void tableSnippetIncludeWithPdfBackend() throws Exception { + File output = configurePdfOutput(); + this.asciidoctor.convert("operation::some-operation[snippets='response-fields']", + this.options); + assertThat(extractStrings(output), + hasItems("Response fields", "Path", "Type", "Description", "a", "Object", + "one", "a.b", "Number", "two", "a.c", "String", "three", "1")); + } + @Test public void includeSnippetInSection() throws Exception { String result = this.asciidoctor.convert( @@ -76,6 +110,16 @@ public class OperationBlockMacroTests { assertThat(result, equalTo(getExpectedContentFromFile("snippet-in-section"))); } + @Test + public void includeSnippetInSectionWithPdfBackend() throws Exception { + File output = configurePdfOutput(); + this.asciidoctor.convert( + "== Section\n" + "operation::some-operation[snippets='curl-request']", + this.options); + assertThat(extractStrings(output), hasItems("Section", "Curl request", + "$ curl 'http://localhost:8080/' -i", "1")); + } + @Test public void includeMultipleSnippets() throws Exception { String result = this.asciidoctor.convert( @@ -163,4 +207,41 @@ public class OperationBlockMacroTests { return attributes; } + private File configurePdfOutput() { + this.options.setBackend("pdf"); + File output = new File("build/output.pdf"); + this.options.setToFile(output.getAbsolutePath()); + return output; + } + + private List extractStrings(File pdfFile) throws IOException { + PDDocument pdf = PDDocument.load(pdfFile); + assertThat(pdf.getNumberOfPages(), equalTo(1)); + StringExtractor stringExtractor = new StringExtractor(); + stringExtractor.processPage(pdf.getPage(0)); + return stringExtractor.getStrings(); + } + + private static final class StringExtractor extends PDFStreamEngine { + + private final List strings = new ArrayList<>(); + + @Override + protected void processOperator(Operator operator, List operands) + throws IOException { + if ("Tj".equals(operator.getName())) { + for (COSBase operand : operands) { + if (operand instanceof COSString) { + this.strings.add((((COSString) operand).getASCII())); + } + } + } + } + + public List getStrings() { + return this.strings; + } + + } + } diff --git a/spring-restdocs-asciidoctor/src/test/resources/operations/all-snippets.html b/spring-restdocs-asciidoctor/src/test/resources/operations/all-snippets.html index 9cb2f024..524e1ee7 100644 --- a/spring-restdocs-asciidoctor/src/test/resources/operations/all-snippets.html +++ b/spring-restdocs-asciidoctor/src/test/resources/operations/all-snippets.html @@ -28,4 +28,40 @@ Host: localhost:8080 + +
+

Response fields

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

a

Object

one

a.b

Number

two

a.c

String

three

+
\ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/resources/operations/snippet-table.html b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet-table.html new file mode 100644 index 00000000..a8241a03 --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/operations/snippet-table.html @@ -0,0 +1,36 @@ +
+

Response fields

+
+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + +
PathTypeDescription

a

Object

one

a.b

Number

two

a.c

String

three

+
+
\ No newline at end of file diff --git a/spring-restdocs-asciidoctor/src/test/resources/some-operation/response-fields.adoc b/spring-restdocs-asciidoctor/src/test/resources/some-operation/response-fields.adoc new file mode 100644 index 00000000..9c0dcd21 --- /dev/null +++ b/spring-restdocs-asciidoctor/src/test/resources/some-operation/response-fields.adoc @@ -0,0 +1,16 @@ +|=== +|Path|Type|Description + +|`a` +|`Object` +|one + +|`a.b` +|`Number` +|two + +|`a.c` +|`String` +|three + +|=== \ No newline at end of file