001package com.credibledoc.substitution.reporting.markdown; 002 003import com.credibledoc.plantuml.exception.PlantumlRuntimeException; 004import com.credibledoc.plantuml.svggenerator.SvgGeneratorService; 005import com.credibledoc.substitution.core.configuration.Configuration; 006import com.credibledoc.substitution.core.context.SubstitutionContext; 007import com.credibledoc.substitution.core.exception.SubstitutionRuntimeException; 008import com.credibledoc.substitution.core.placeholder.Placeholder; 009import com.credibledoc.substitution.core.resource.ResourceService; 010import com.credibledoc.substitution.reporting.placeholder.PlaceholderToReportDocumentService; 011import com.credibledoc.substitution.core.replacement.ReplacementType; 012import com.credibledoc.substitution.reporting.reportdocument.ReportDocument; 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016import java.io.File; 017import java.io.FileOutputStream; 018import java.io.OutputStream; 019 020/** 021 * This singleton helps to parse templates from the {@link Configuration#getTemplatesResource()} folder, extract 022 * {@link Placeholder}s between {@link Configuration#getPlaceholderBegin()} and {@link Configuration#getPlaceholderEnd()} 023 * tags, create {@link ReportDocument}s which represent a content of the placeholders and generate new documents in the 024 * {@link Configuration#getTargetDirectory()} with the new content instead of placeholders. 025 * 026 * @author Kyrylo Semenko 027 */ 028public class MarkdownService { 029 private static final Logger logger = LoggerFactory.getLogger(MarkdownService.class); 030 private static final String SLASH = "/"; 031 private static final String IMAGE_DIRECTORY_NAME = "img"; 032 private static final String SVG_FILE_EXTENSION = ".svg"; 033 private static final String SVG_TAG_BEGIN = ""; 036 private static final String SYNTAX_ERROR_GENERATED_KEYWORD = "Syntax Error?"; 037 private static final String IGNORE_SYNTAX_ERROR_PLACEHOLDER_PARAMETER = "ignoreSyntaxError"; 038 039 /** 040 * Singleton. 041 */ 042 private static final MarkdownService instance = new MarkdownService(); 043 044 /** 045 * @return The {@link MarkdownService} singleton. 046 */ 047 public static MarkdownService getInstance() { 048 return instance; 049 } 050 051 /** 052 * Generate SVG image file and a link to the file. 053 * <ul> 054 * <li>Load template from the {@link Placeholder#getResource()} field</li> 055 * <li>Create a new file from the template in the {@link Configuration#getTargetDirectory()} directory</li> 056 * <li>Create a new {@link #IMAGE_DIRECTORY_NAME} directory</li> 057 * <li>Get a {@link ReportDocument} form the {@link PlaceholderToReportDocumentService}</li> 058 * <li>Join lines from the {@link ReportDocument#getCacheLines()} list</li> 059 * <li>And return result of the 060 * {@link #generateSvgFileAndTagForMarkdown(File, File, String, Placeholder, boolean)} method</li> 061 * </ul> 062 * 063 * @param placeholder the state object 064 * @param plantUml PlantUML source notation. If the value is 'null', the value will be obtained from 065 * {@link ReportDocument} which is stored in 066 * {@link PlaceholderToReportDocumentService#getReportDocument(Placeholder)}. 067 * @param substitutionContext the current state 068 * @return A part of markdown document with link to generated SVG image. 069 */ 070 public String generateDiagram(Placeholder placeholder, String plantUml, SubstitutionContext substitutionContext) { 071 ResourceService resourceService = ResourceService.getInstance(); 072 String placeholderResourceRelativePath = 073 resourceService.generatePlaceholderResourceRelativePath(placeholder.getResource(), substitutionContext); 074 075 Configuration configuration = substitutionContext.getConfiguration(); 076 File mdFile = new File(configuration.getTargetDirectory() + placeholderResourceRelativePath); 077 File directory = mdFile.getParentFile(); 078 File imageDirectory = new File(directory, IMAGE_DIRECTORY_NAME); 079 createDirectoryIfNotExists(imageDirectory); 080 if (plantUml == null) { 081 ReportDocument reportDocument = PlaceholderToReportDocumentService.getInstance() 082 .getReportDocument(placeholder); 083 plantUml = String.join(System.lineSeparator(), reportDocument.getCacheLines()); 084 } 085 String placeholderDescription = placeholder.getDescription(); 086 087 if (plantUml.isEmpty()) { 088 return "Cannot generate diagram because source content not found. " + 089 "PlaceholderDescription: '" + placeholderDescription + "'."; 090 } 091 092 boolean replaceFilterId = "true".equals(substitutionContext.getConfiguration().getReplaceFilterId()); 093 return generateSvgFileAndTagForMarkdown( 094 mdFile, 095 imageDirectory, 096 plantUml, 097 placeholder, 098 replaceFilterId 099 ); 100 } 101 102 private String generateSvgFileAndTagForMarkdown(File mdFile, 103 File imageDirectory, 104 String plantUml, 105 Placeholder placeholder, 106 boolean replaceFilterId) { 107 try { 108 String svg = SvgGeneratorService.getInstance().generateSvgFromPlantUml(plantUml); 109 110 if (replaceFilterId) { 111 svg = replaceFilterId(svg); 112 } 113 114 File svgFile = new File(imageDirectory, 115 mdFile.getName() + "_" + placeholder.getId() + SVG_FILE_EXTENSION); 116 117 try (OutputStream outputStream = new FileOutputStream(svgFile)) { 118 outputStream.write(svg.getBytes()); 119 } 120 logger.debug("File created: {}", svgFile.getAbsolutePath()); 121 122 boolean ignoreSyntaxError = placeholder.getParameters() 123 .containsKey(IGNORE_SYNTAX_ERROR_PLACEHOLDER_PARAMETER) && 124 placeholder.getParameters().get(IGNORE_SYNTAX_ERROR_PLACEHOLDER_PARAMETER).equals("false"); 125 126 if (!ignoreSyntaxError && svg.contains(SYNTAX_ERROR_GENERATED_KEYWORD)) { 127 throw new PlantumlRuntimeException("SVG contains '" + SYNTAX_ERROR_GENERATED_KEYWORD 128 + "' substring. SVG: '" + svg 129 + "'. " + System.lineSeparator() 130 + placeholder); 131 } 132 if (placeholder.getParameters().get(ReplacementType.TARGET_FORMAT) != null) { 133 ReplacementType replacementType = 134 ReplacementType.valueOf(placeholder.getParameters().get(ReplacementType.TARGET_FORMAT)); 135 if (ReplacementType.HTML_EMBEDDED == replacementType) { 136 return svg; 137 } 138 throw new SubstitutionRuntimeException("Unknown " + ReplacementType.class.getSimpleName() + " " + 139 "value " + replacementType); 140 } else { 141 return SVG_TAG_BEGIN + placeholder.getDescription() + SVG_TAG_MIDDLE + imageDirectory.getName() + 142 SLASH + svgFile.getName() + SVG_TAG_END; 143 } 144 } catch (Exception e) { 145 throw new SubstitutionRuntimeException(e); 146 } 147 } 148 149 /** 150 * Replace all occurrences of the generated ids with constants. Example of filter line: 151 * <pre>{@code 152 * <filter height="300%" id="f10gnta8ifhhre" width="300%" x="-1" y="-1"> 153 * }</pre> 154 * @param svg for replacing 155 * @return The svg with replaced ids, for example id="f10gnta8ifhhre" will be replaced with id="1" 156 */ 157 private String replaceFilterId(String svg) { 158 int beginFilter = svg.indexOf("<filter "); 159 if (beginFilter == -1) { 160 return svg; 161 } 162 String beginIdPattern = " id=\""; 163 int beginId = svg.indexOf(beginIdPattern, beginFilter); 164 if (beginId == -1) { 165 return svg; 166 } 167 int endId = svg.indexOf("\" ", beginId + beginIdPattern.length()); 168 String oldId = svg.substring(beginId + beginIdPattern.length(), endId); 169 170 return svg.replace(oldId, "1"); 171 } 172 173 private void createDirectoryIfNotExists(File directory) { 174 if (!directory.exists()) { 175 boolean created = directory.mkdirs(); 176 if (!created) { 177 throw new SubstitutionRuntimeException("Cannot create a new directory '" + 178 directory.getAbsolutePath() + "'"); 179 } 180 logger.info("The new directory created '{}'", directory.getAbsolutePath()); 181 } 182 } 183}