All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.thucydides.core.reports.html.Formatter Maven / Gradle / Ivy

There is a newer version: 4.1.20
Show newest version
package net.thucydides.core.reports.html;

import com.github.rjeschke.txtmark.Configuration;
import com.github.rjeschke.txtmark.Processor;
import com.google.common.base.Splitter;
import net.serenitybdd.core.collect.NewList;
import com.google.inject.Inject;
import com.google.inject.Key;
import net.thucydides.core.ThucydidesSystemProperty;
import net.thucydides.core.guice.Injectors;
import net.thucydides.core.issues.IssueTracking;
import net.thucydides.core.reports.renderer.Asciidoc;
import net.thucydides.core.reports.renderer.MarkupRenderer;
import net.thucydides.core.util.EnvironmentVariables;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.translate.AggregateTranslator;
import org.apache.commons.lang3.text.translate.CharSequenceTranslator;
import org.apache.commons.lang3.text.translate.EntityArrays;
import org.apache.commons.lang3.text.translate.LookupTranslator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static net.thucydides.core.reports.html.MarkdownRendering.RenderedElements.*;
import static org.apache.commons.lang3.StringUtils.abbreviate;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.trim;


//////

import com.vladsch.flexmark.ast.Node;
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.options.MutableDataSet;

/**
 * Format text for HTML reports.
 * In particular, this integrates JIRA links into the generated reports.
 */
public class Formatter {

    private final static String ISSUE_NUMBER_REGEXP = "#([A-Z][A-Z0-9-_]*)?-?\\d+";
    private final static Pattern shortIssueNumberPattern = Pattern.compile(ISSUE_NUMBER_REGEXP);
    private final static String FULL_ISSUE_NUMBER_REGEXP = "([A-Z][A-Z0-9-_]*)-\\d+";
    private final static Pattern fullIssueNumberPattern = Pattern.compile(FULL_ISSUE_NUMBER_REGEXP);
    private final static String ISSUE_LINK_FORMAT = "{1}";
    private static final String ELIPSE = "…";
    private static final String ASCIIDOC = "asciidoc";
    private static final String MARKDOWN = "markdown";
    private static final String TEXT = "";
    private static final String STANDARD_NEW_LINE = "\n";

    private final static String NEWLINE_CHAR = "\u2424";
    private final static String NEWLINE = "\u0085";
    private final static String LINE_SEPARATOR = "\u2028";
    private final static String PARAGRAPH_SEPARATOR = "\u2029";

    private final static Logger LOGGER = LoggerFactory.getLogger(Formatter.class);
    public static final String FOUR_SPACES = "       ";
    public static final String TAB = "\\t";
    public static final String NEW_LINE_ON_ANY_OS = "\\r?\\n";
    public static final String UTF_8_NEW_LINE = "␤";

    public static String[][] UNICODE_CHARS_ESCAPE = new String[][]{{"\\u", "\"}};

    private final IssueTracking issueTracking;
    private final EnvironmentVariables environmentVariables;
    private final MarkupRenderer asciidocRenderer;
//    private final Markdown4jProcessor markdown4jProcessor;
    Configuration markdownEncodingConfiguration;

    Parser parser;
    HtmlRenderer renderer;

    @Inject
    public Formatter(IssueTracking issueTracking, EnvironmentVariables environmentVariables) {
        this.issueTracking = issueTracking;
        this.environmentVariables = environmentVariables;
        this.asciidocRenderer = Injectors.getInjector().getInstance(Key.get(MarkupRenderer.class, Asciidoc.class));

        String encoding = ThucydidesSystemProperty.REPORT_CHARSET.from(environmentVariables,"UTF-8");
        markdownEncodingConfiguration = Configuration.builder().setEncoding(encoding).build();


        /////////////
        MutableDataSet options = new MutableDataSet();

        // uncomment to set optional extensions
        //options.set(Parser.EXTENSIONS, Arrays.asList(TablesExtension.create(), StrikethroughExtension.create()));

        // uncomment to convert soft-breaks to hard breaks
        //options.set(HtmlRenderer.SOFT_BREAK, "
\n"); parser = Parser.builder(options).build(); renderer = HtmlRenderer.builder(options).build(); } public Formatter(IssueTracking issueTracking) { this(issueTracking, Injectors.getInjector().getProvider(EnvironmentVariables.class).get() ); } public String renderAsciidoc(String text) { return stripNewLines(asciidocRenderer.render(text)); } public String renderMarkdown(String text) { if (text == null) { return ""; } Node document = parser.parse(text); String html = renderer.render(document); return stripSurroundingParagraphTagsFrom(html); } private String stripSurroundingParagraphTagsFrom(String text) { if (startsWithParagraphTag(text) && endWithParagraphTag(text)) { text = trim(text).substring(3); text = text.substring(0, text.length() - 4); } return text; } private boolean startsWithParagraphTag(String text) { return trim(text.toLowerCase()).startsWith("

"); } private boolean endWithParagraphTag(String text) { return trim(text.toLowerCase()).endsWith("

"); } private String stripNewLines(String render) { return render.replaceAll("\n", ""); } public String stripQualifications(String title) { if (title == null) { return ""; } if (title.contains("[")) { return title.substring(0,title.lastIndexOf("[")).trim(); } else { return title; } } public String renderText(String text) { if (isEmpty(text)) { return ""; } return concatLines(BASIC_XML.translate(text),"
") .replaceAll(TAB, FOUR_SPACES); } public String renderHeaders(String text) { if (text == null) { return ""; } return concatLines(BASIC_XML.translate(stringFormOf(text)),"
") .replaceAll("\\t", ""); } static class IssueExtractor { private String workingCopy; IssueExtractor(String initialValue) { this.workingCopy = initialValue; } public List getShortenedIssues() { Matcher matcher = shortIssueNumberPattern.matcher(workingCopy); ArrayList issues = new ArrayList<>(); while (matcher.find()) { String issue = matcher.group(); issues.add(issue); workingCopy = workingCopy.replaceFirst(issue, ""); } return issues; } public List getFullIssues() { Matcher unhashedMatcher = fullIssueNumberPattern.matcher(workingCopy); ArrayList issues = new ArrayList<>(); while (unhashedMatcher.find()) { String issue = unhashedMatcher.group(); issues.add(issue); workingCopy = workingCopy.replaceFirst(issue, ""); } return issues; } } public static List issuesIn(final String value) { IssueExtractor extractor = new IssueExtractor(value); List issuesWithHash = extractor.getShortenedIssues(); List allIssues = extractor.getFullIssues(); allIssues.addAll(issuesWithHash); return allIssues; } public String addLinks(final String value) { if (issueTracking == null) { return value; } String formattedValue = value; if (issueTracking.getIssueTrackerUrl() != null) { formattedValue = insertFullIssueTrackingUrls(value); } if (issueTracking.getShortenedIssueTrackerUrl() != null) { formattedValue = insertShortenedIssueTrackingUrls(formattedValue); } return formattedValue; } public String renderDescription(final String text) { String format = environmentVariables.getProperty(ThucydidesSystemProperty.NARRATIVE_FORMAT, TEXT); if (isRenderedHtml(text)) { return text; } else if (format.equalsIgnoreCase(ASCIIDOC)) { // Use ASCIIDOC if configured return renderAsciidoc(text); } else if (format.equalsIgnoreCase(MARKDOWN) || (MarkdownRendering.configuredIn(environmentVariables).renderMarkdownFor(narrative)) ) { return renderMarkdown(text); } else { return addLineBreaks(text); } } private boolean isRenderedHtml(String text) { return (text != null) && (text.startsWith("<")); } public static String addLineBreaks(final String text) { return (text != null) ? concatLines(text.trim(), "
") : ""; } public String convertAnyTables(String text) { if (shouldFormatEmbeddedTables() && containsEmbeddedTable(text)) { text = convertNonStandardNLChars(text); text = ExampleTable.stripBracketsFromOuterPipes(text); text = withTablesReplaced(text); } return text; } private String withTablesReplaced(String text) { List unformattedTables = getEmbeddedTablesIn(text); for(String unformattedTable : unformattedTables) { ExampleTable table = new ExampleTable(unformattedTable); text = text.replace(unformattedTable, table.inHtmlFormat()); } text = text.replaceAll(newLineUsedIn(text), "
"); return text; } private String convertNonStandardNLChars(String text) { text = StringUtils.replace(text, NEWLINE_CHAR, STANDARD_NEW_LINE); text = StringUtils.replace(text, NEWLINE, STANDARD_NEW_LINE); text = StringUtils.replace(text, LINE_SEPARATOR, STANDARD_NEW_LINE); text = StringUtils.replace(text, PARAGRAPH_SEPARATOR, STANDARD_NEW_LINE); return text; } private boolean shouldFormatEmbeddedTables() { return !(ThucydidesSystemProperty.IGNORE_EMBEDDED_TABLES.booleanFrom(environmentVariables)); } private boolean containsEmbeddedTable(String text) { return ((positionOfFirstPipeIn(text) >= 0) && (positionOfLastPipeIn(text) >= 0)); } private int positionOfLastPipeIn(String text) { return text.indexOf("|", positionOfFirstPipeIn(text) + 1); } private int positionOfFirstPipeIn(String text) { return text.indexOf("|"); } private List getEmbeddedTablesIn(String text) { List embeddedTables = new ArrayList<>(); StringBuffer tableText = new StringBuffer(); try (BufferedReader reader = new BufferedReader(new StringReader(text))) { boolean inTable = false; String newLine = newLineUsedIn(text); String line; while ((line = reader.readLine()) != null) { if (!inTable && line.contains("|")){ // start of a table inTable = true; } else if (inTable && !line.contains("|") && !(isBlank(line))){ // end of a table embeddedTables.add(tableText.toString().trim()); tableText = new StringBuffer(); inTable = false; } if (inTable) { tableText.append(line).append(newLine); } } } catch (IOException e) { throw new IllegalArgumentException("Could not process embedded table", e); } if (!tableText.toString().isEmpty()) { embeddedTables.add(tableText.toString().trim()); } return embeddedTables; } private boolean isBlank(String line) { return (StringUtils.isBlank(line.trim())); } private String newLineUsedIn(String text) { if (text.contains("\r\n")) { return "\r\n"; } else if (text.contains("\n")) { return "\n"; } else if (text.contains("\r")) { return "\r"; } else { return STANDARD_NEW_LINE; } } private static final CharSequenceTranslator ESCAPE_SPECIAL_CHARS = new AggregateTranslator( new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE()), new LookupTranslator(EntityArrays.HTML40_EXTENDED_ESCAPE()) ); private final CharSequenceTranslator BASIC_XML = new AggregateTranslator( new LookupTranslator(EntityArrays.BASIC_ESCAPE()), new LookupTranslator(UNICODE_CHARS_ESCAPE) ); public String htmlCompatible(Object fieldValue) { return plainHtmlCompatible(fieldValue); } public String messageBody(String message) { return renderText(message.trim()); } public String restQuery(String message) { return renderText(message.trim()); } public String htmlCompatibleStoryTitle(Object fieldValue) { String firstLine = fieldValue.toString().split("\\n")[0]; return (MarkdownRendering.configuredIn(environmentVariables).renderMarkdownFor(story)) ? (htmlCompatible(renderMarkdown(firstLine))) : htmlCompatible(firstLine); } public String htmlCompatibleTestTitle(Object fieldValue) { String firstLine = fieldValue.toString().split("\\n")[0]; return (MarkdownRendering.configuredIn(environmentVariables).renderMarkdownFor(scenario)) ? (htmlCompatible(renderMarkdown(firstLine))) : htmlCompatible(firstLine); } public String htmlCompatibleStepDescription(Object fieldValue) { return (MarkdownRendering.configuredIn(environmentVariables).renderMarkdownFor(step)) ? (htmlCompatible(renderMarkdown(fieldValue.toString()))) : htmlCompatible(fieldValue); } public String plainHtmlCompatible(Object fieldValue) { return addLineBreaks(ESCAPE_SPECIAL_CHARS.translate(fieldValue != null ? stringFormOf(fieldValue) : "")).trim(); } public String htmlAttributeCompatible(Object fieldValue) { if (fieldValue == null) { return ""; } return concatLines(ESCAPE_SPECIAL_CHARS.translate(stringFormOf(fieldValue) .replaceAll("<", "(") .replaceAll(">", ")") .replaceAll("\"", "'"))); } public String htmlAttributeCompatible(Object fieldValue, int maxLength) { return abbreviate(htmlAttributeCompatible(fieldValue), maxLength); } public ResultIconFormatter resultIcon() { return new ResultIconFormatter(); } public ResultRankingFormatter resultRank() { return new ResultRankingFormatter(); } private static String concatLines(String message) { return concatLines(message," "); } private static String concatLines(String message, String newLine) { message = StringUtils.replace(message, UTF_8_NEW_LINE, newLine); List lines = Splitter.onPattern(NEW_LINE_ON_ANY_OS).splitToList(message); return StringUtils.join(lines,newLine); } private static String stringFormOf(Object fieldValue) { if (Iterable.class.isAssignableFrom(fieldValue.getClass())) { return "[" + StringUtils.join((Iterable)fieldValue, ", ") +"]"; } else { return fieldValue.toString(); } } public String truncatedHtmlCompatible(String text, int length) { return htmlCompatible(text); // return renderMarkdown(addLineBreaks(ESCAPE_SPECIAL_CHARS.translate(truncate(text, length)))); // return ESCAPE_SPECIAL_CHARS.translate(renderMarkdown(addLineBreaks(truncate(text, length)))); } private String replaceWithTokens(String value, List issues) { List sortedIssues = inOrderOfDecreasingLength(issues); for(int i = 0; i < sortedIssues.size(); i++) { value = value.replaceAll(sortedIssues.get(i), "%%%" + i + "%%%"); } return value; } private List inOrderOfDecreasingLength(List issues) { List sortedIssues = NewList.copyOf(issues); Collections.sort(sortedIssues, new Comparator() { @Override public int compare(String o1, String o2) { return o2.length() - o1.length(); } }); return sortedIssues; } public static List shortenedIssuesIn(String value) { IssueExtractor extractor = new IssueExtractor(value); return extractor.getShortenedIssues(); } public static List fullIssuesIn(String value) { IssueExtractor extractor = new IssueExtractor(value); return extractor.getFullIssues(); } private String insertShortenedIssueTrackingUrls(String value) { String issueUrlFormat = issueTracking.getShortenedIssueTrackerUrl(); List issues = sortByDecreasingSize(shortenedIssuesIn(value)); String formattedValue = replaceWithTokens(value, issues); int i = 0; for (String issue : issues) { String issueUrl = MessageFormat.format(issueUrlFormat, stripLeadingHashFrom(issue)); String issueLink = MessageFormat.format(ISSUE_LINK_FORMAT, issueUrl, issue); String token = "%%%" + i++ + "%%%"; formattedValue = formattedValue.replaceAll(token, issueLink); } return formattedValue; } private String insertFullIssueTrackingUrls(String value) { String issueUrlFormat = issueTracking.getIssueTrackerUrl(); List issues = sortByDecreasingSize(fullIssuesIn(value)); String formattedValue = replaceWithTokens(value, issues); int i = 0; for (String issue : issues) { String issueUrl = MessageFormat.format(issueUrlFormat, issue); String issueLink = MessageFormat.format(ISSUE_LINK_FORMAT, issueUrl, issue); String token = "%%%" + i++ + "%%%"; formattedValue = formattedValue.replaceAll(token, issueLink); } return formattedValue; } private List sortByDecreasingSize(List issues) { List sortedIssues = new ArrayList<>(issues); Collections.sort(sortedIssues, (a, b) -> Integer.valueOf(-a.length()).compareTo(Integer.valueOf(b.length()))); return sortedIssues; } public String formatWithFields(String textToFormat) { String textWithEscapedFields = textToFormat.replaceAll("<", "<") .replaceAll(">", ">"); String renderedText = addLineBreaks(removeMacros(convertAnyTables(textWithEscapedFields))); if (MarkdownRendering.configuredIn(environmentVariables).renderMarkdownFor(step)) { renderedText = renderMarkdown(renderedText); } return renderedText; } private String removeMacros(String textToFormat) { return textToFormat.replaceAll("\\{trim=false\\}\\s*\\r?\\n",""); } private String stripLeadingHashFrom(final String issue) { return issue.substring(1); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy