Avoid problems caused by | characters in table cell content
Previously, a | character in a cell of an Asciidoctor table would break that table's formatting as it would be interpretted as a delimiter for the cell. The problem also affects Markdown tables where the | character is not within backticks. This commit addresses the problem with Asciidoctor tables by introducing a JMustache Lambda that escapes | characters by prefixing them with a backslash. Unfortunately, a complete solution to the problem when using Markdown is not as straightforward. Markdown only requires a | character to be espaced when it is not within backticks. Determing this isn't straightforward as the text would have to be parsed, taking into account the possibility of backticks themselves being escaped. Instead, this commit attempts to avoid the problem in a different way. The most likely source of a | character is when documenting a `@Pattern` constraint that uses a | character in its regular expression. To improve the readability of the regular expression this commit wraps it in backticks, thereby formatting it in a monospaced font in both Asciidoctor and Markdown and also avoiding any escaping problems with the latter. Closes gh-232 See gh-230
This commit is contained in:
@@ -17,15 +17,19 @@
|
||||
package org.springframework.restdocs.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.RestDocumentationContext;
|
||||
import org.springframework.restdocs.mustache.Mustache;
|
||||
import org.springframework.restdocs.snippet.RestDocumentationContextPlaceholderResolver;
|
||||
import org.springframework.restdocs.snippet.StandardWriterResolver;
|
||||
import org.springframework.restdocs.snippet.WriterResolver;
|
||||
import org.springframework.restdocs.templates.StandardTemplateResourceResolver;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.templates.TemplateFormats;
|
||||
import org.springframework.restdocs.templates.mustache.AsciidoctorTableCellContentLambda;
|
||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||
|
||||
/**
|
||||
@@ -102,9 +106,16 @@ public abstract class RestDocumentationConfigurer<S extends AbstractConfigurer,
|
||||
if (engineToUse == null) {
|
||||
SnippetConfiguration snippetConfiguration = (SnippetConfiguration) configuration
|
||||
.get(SnippetConfiguration.class.getName());
|
||||
Map<String, Object> templateContext = new HashMap<>();
|
||||
if (snippetConfiguration.getTemplateFormat().getId()
|
||||
.equals(TemplateFormats.asciidoctor().getId())) {
|
||||
templateContext.put("tableCellContent",
|
||||
new AsciidoctorTableCellContentLambda());
|
||||
}
|
||||
engineToUse = new MustacheTemplateEngine(
|
||||
new StandardTemplateResourceResolver(
|
||||
snippetConfiguration.getTemplateFormat()));
|
||||
snippetConfiguration.getTemplateFormat()),
|
||||
Mustache.compiler().escapeHTML(false), templateContext);
|
||||
}
|
||||
configuration.put(TemplateEngine.class.getName(), engineToUse);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed 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.springframework.restdocs.templates.mustache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.springframework.restdocs.mustache.Mustache.Lambda;
|
||||
import org.springframework.restdocs.mustache.Template.Fragment;
|
||||
|
||||
/**
|
||||
* A {@link Lambda} that escapes {@code |} characters so that the do not break the table's
|
||||
* formatting.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public final class AsciidoctorTableCellContentLambda implements Lambda {
|
||||
|
||||
@Override
|
||||
public void execute(Fragment fragment, Writer writer) throws IOException {
|
||||
String output = fragment.execute();
|
||||
for (int i = 0; i < output.length(); i++) {
|
||||
char current = output.charAt(i);
|
||||
if (current == '|' && (i == 0 || output.charAt(i - 1) != '\\')) {
|
||||
writer.append('\\');
|
||||
}
|
||||
writer.append(current);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.restdocs.templates.mustache;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.templates.Template;
|
||||
@@ -30,18 +32,37 @@ public class MustacheTemplate implements Template {
|
||||
|
||||
private final org.springframework.restdocs.mustache.Template delegate;
|
||||
|
||||
private final Map<String, Object> context;
|
||||
|
||||
/**
|
||||
* Creates a new {@code MustacheTemplate} that adapts the given {@code delegate}.
|
||||
*
|
||||
* @param delegate The delegate to adapt
|
||||
*/
|
||||
public MustacheTemplate(org.springframework.restdocs.mustache.Template delegate) {
|
||||
this(delegate, Collections.<String, Object>emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code MustacheTemplate} that adapts the given {@code delegate}.
|
||||
* During rendering, the given {@code context} and the context passed into
|
||||
* {@link #render(Map)} will be combined and then passed to the delegate when it is
|
||||
* {@link org.springframework.restdocs.mustache.Template#execute executed}.
|
||||
*
|
||||
* @param delegate The delegate to adapt
|
||||
* @param context The context
|
||||
*/
|
||||
public MustacheTemplate(org.springframework.restdocs.mustache.Template delegate,
|
||||
Map<String, Object> context) {
|
||||
this.delegate = delegate;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String render(Map<String, Object> context) {
|
||||
return this.delegate.execute(context);
|
||||
Map<String, Object> combinedContext = new HashMap<>(this.context);
|
||||
combinedContext.putAll(context);
|
||||
return this.delegate.execute(combinedContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.restdocs.templates.mustache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.restdocs.mustache.Mustache;
|
||||
@@ -36,9 +38,11 @@ import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||
*/
|
||||
public class MustacheTemplateEngine implements TemplateEngine {
|
||||
|
||||
private final TemplateResourceResolver templateResourceResolver;
|
||||
|
||||
private final Compiler compiler;
|
||||
|
||||
private final TemplateResourceResolver templateResourceResolver;
|
||||
private final Map<String, Object> context;
|
||||
|
||||
/**
|
||||
* Creates a new {@code MustacheTemplateEngine} that will use the given
|
||||
@@ -60,16 +64,36 @@ public class MustacheTemplateEngine implements TemplateEngine {
|
||||
*/
|
||||
public MustacheTemplateEngine(TemplateResourceResolver templateResourceResolver,
|
||||
Compiler compiler) {
|
||||
this(templateResourceResolver, compiler, Collections.<String, Object>emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code MustacheTemplateEngine} that will use the given
|
||||
* {@code templateResourceResolver} to resolve templates and the given
|
||||
* {@code compiler} to compile them. Compiled templates will be created with the given
|
||||
* {@code context}.
|
||||
*
|
||||
* @param templateResourceResolver the resolver to use
|
||||
* @param compiler the compiler to use
|
||||
* @param context the context to pass to compiled templates
|
||||
* @see MustacheTemplate#MustacheTemplate(org.springframework.restdocs.mustache.Template,
|
||||
* Map)
|
||||
*/
|
||||
public MustacheTemplateEngine(TemplateResourceResolver templateResourceResolver,
|
||||
Compiler compiler, Map<String, Object> context) {
|
||||
this.templateResourceResolver = templateResourceResolver;
|
||||
this.compiler = compiler;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Template compileTemplate(String name) throws IOException {
|
||||
Resource templateResource = this.templateResourceResolver
|
||||
.resolveTemplateResource(name);
|
||||
return new MustacheTemplate(this.compiler
|
||||
.compile(new InputStreamReader(templateResource.getInputStream())));
|
||||
return new MustacheTemplate(
|
||||
this.compiler.compile(
|
||||
new InputStreamReader(templateResource.getInputStream())),
|
||||
this.context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ javax.validation.constraints.Min.description=Must be at least ${value}
|
||||
javax.validation.constraints.NotNull.description=Must not be null
|
||||
javax.validation.constraints.Null.description=Must be null
|
||||
javax.validation.constraints.Past.description=Must be in the past
|
||||
javax.validation.constraints.Pattern.description=Must match the regular expression '${regexp}'
|
||||
javax.validation.constraints.Pattern.description=Must match the regular expression `${regexp}`
|
||||
javax.validation.constraints.Size.description=Size must be between ${min} and ${max} inclusive
|
||||
org.hibernate.validator.constraints.CreditCardNumber.description=Must be a well-formed credit card number
|
||||
org.hibernate.validator.constraints.EAN.description=Must be a well-formed ${type} number
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|Relation|Description
|
||||
|
||||
{{#links}}
|
||||
|`{{rel}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{rel}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/links}}
|
||||
|===
|
||||
@@ -3,8 +3,8 @@
|
||||
|Parameter|Description
|
||||
|
||||
{{#parameters}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/parameters}}
|
||||
|===
|
||||
@@ -2,9 +2,9 @@
|
||||
|Path|Type|Description
|
||||
|
||||
{{#fields}}
|
||||
|`{{path}}`
|
||||
|`{{type}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{path}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}`{{type}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/fields}}
|
||||
|===
|
||||
@@ -2,8 +2,8 @@
|
||||
|Name|Description
|
||||
|
||||
{{#headers}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/headers}}
|
||||
|===
|
||||
@@ -2,8 +2,8 @@
|
||||
|Parameter|Description
|
||||
|
||||
{{#parameters}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/parameters}}
|
||||
|===
|
||||
@@ -2,8 +2,8 @@
|
||||
|Part|Description
|
||||
|
||||
{{#requestParts}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/requestParts}}
|
||||
|===
|
||||
@@ -2,9 +2,9 @@
|
||||
|Path|Type|Description
|
||||
|
||||
{{#fields}}
|
||||
|`{{path}}`
|
||||
|`{{type}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{path}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}`{{type}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/fields}}
|
||||
|===
|
||||
@@ -2,8 +2,8 @@
|
||||
|Name|Description
|
||||
|
||||
{{#headers}}
|
||||
|`{{name}}`
|
||||
|{{description}}
|
||||
|{{#tableCellContent}}`{{name}}`{{/tableCellContent}}
|
||||
|{{#tableCellContent}}{{description}}{{/tableCellContent}}
|
||||
|
||||
{{/headers}}
|
||||
|===
|
||||
Reference in New Issue
Block a user