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
This commit is contained in:
Andy Wilkinson
2017-05-30 12:53:16 +01:00
parent 56a0495288
commit ba327f1e9a
7 changed files with 179 additions and 4 deletions

View File

@@ -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'

View File

@@ -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'
}

View File

@@ -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)

View File

@@ -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<String> 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<String> strings = new ArrayList<>();
@Override
protected void processOperator(Operator operator, List<COSBase> operands)
throws IOException {
if ("Tj".equals(operator.getName())) {
for (COSBase operand : operands) {
if (operand instanceof COSString) {
this.strings.add((((COSString) operand).getASCII()));
}
}
}
}
public List<String> getStrings() {
return this.strings;
}
}
}

View File

@@ -28,4 +28,40 @@ Host: localhost:8080</code></pre>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_response_fields">Response fields</h2>
<div class="sectionbody">
<table class="tableblock frame-all grid-all spread">
<colgroup>
<col style="width: 33.3333%;">
<col style="width: 33.3333%;">
<col style="width: 33.3334%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Path</th>
<th class="tableblock halign-left valign-top">Type</th>
<th class="tableblock halign-left valign-top">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>a</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Object</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">one</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>a.b</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Number</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">two</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>a.c</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">three</p></td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,36 @@
<div class="sect1">
<h2 id="_response_fields">Response fields</h2>
<div class="sectionbody">
<table class="tableblock frame-all grid-all spread">
<colgroup>
<col style="width: 33.3333%;">
<col style="width: 33.3333%;">
<col style="width: 33.3334%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Path</th>
<th class="tableblock halign-left valign-top">Type</th>
<th class="tableblock halign-left valign-top">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>a</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Object</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">one</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>a.b</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>Number</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">two</p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>a.c</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><code>String</code></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">three</p></td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,16 @@
|===
|Path|Type|Description
|`a`
|`Object`
|one
|`a.b`
|`Number`
|two
|`a.c`
|`String`
|three
|===