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

com.credibledoc.plantuml.svggenerator.SvgGeneratorService Maven / Gradle / Ivy

package com.credibledoc.plantuml.svggenerator;

import com.credibledoc.plantuml.exception.PlantumlRuntimeException;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.FileFormatOption;
import net.sourceforge.plantuml.SourceStringReader;
import net.sourceforge.plantuml.core.DiagramDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;

/**
 * This class provides a service for generating SVG files from PlantUML notations,
 * see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics
 * and https://en.wikipedia.org/wiki/PlantUML
 *
 * @author Kyrylo Semenko
 */
public class SvgGeneratorService {
    
    private static final String FORBIDDEN_SEQUENCE_FOR_XML_DOCUMENT = "[#";

    private static final String ENDUML = "@enduml";

    private static final String LINE_SEPARATOR = System.lineSeparator();


    private static final String STARTUML = "@startuml";

    private static final Logger logger = LoggerFactory.getLogger(SvgGeneratorService.class);
    
    private static final String TAG_G_SVG = "";

    /**
     * Singleton.
     */
    private static final SvgGeneratorService instance = new SvgGeneratorService();

    private SvgGeneratorService() {
        // empty
    }

    /**
     * @return An existing {@link #instance}. If the instance is 'null',
     * instantiate new {@link SvgGeneratorService}.
     */
    public static SvgGeneratorService getInstance() {
        return instance;
    }

    /**
     * Call the {@link #generateSvgFromPlantUml(String, boolean)}
     * method with true as the second argument.
     *
     * @param plantUml see the {@link #generateSvgFromPlantUml(String, boolean)}
     *                 method description
     * @return see the {@link #generateSvgFromPlantUml(String, boolean)}
     * method description
     */
    public String generateSvgFromPlantUml(String plantUml) {
        return generateSvgFromPlantUml(plantUml, true);
    }

    /**
     * 

* Generate a SVG content from PlantUML notations. *

* For launching of the generator, the Graphviz tool should be installed, * see http://plantuml.com/graphviz-dot. *

* If the plantUml notations do not begin with @startuml tag, * attach the tag to the beginning of the plantUml notations. *

* If the plantUml notations do not ends by @enduml tag, * append the tag to the ent of plantUml *

* Append commented plantUml to the end of SVG * * @param printWarningAndPlantUml if 'true', the #plantUml content will be escaped and printed at the end of * svg as a comment for humans readability. * @param plantUml source string, for example

{@code Bob -> Alice : hello\nAlice -> Bob : hi}
* @param formatSvg if 'true', a formatted SVG content will be returned * by calling the {@link #formatSvg(String)} method, * but in case when the source plantUml content has * the [# sequence, do NOT format the SVG content. * @return SVG content. */ public String generateSvgFromPlantUml(boolean printWarningAndPlantUml, String plantUml, boolean formatSvg) { try (final ByteArrayOutputStream os = new ByteArrayOutputStream()) { if (!plantUml.trim().startsWith(STARTUML)) { plantUml = STARTUML + LINE_SEPARATOR + plantUml; } if (!plantUml.trim().endsWith(ENDUML)) { plantUml = plantUml + LINE_SEPARATOR + ENDUML; } SourceStringReader reader = new SourceStringReader(plantUml); FileFormatOption fileFormatOption = new FileFormatOption(FileFormat.SVG); DiagramDescription diagramDescription = reader.outputImage(os, fileFormatOption); logger.info("DiagramDescription: {}", diagramDescription.getDescription()); // The XML is stored into svg String svg = os.toString(StandardCharsets.UTF_8.name()); if (printWarningAndPlantUml) { final String warningMessage = "!WARNING! Original strings (double dash) has been replaced" + " with '- -' (dash+space+dash) in this comment" + ", because the string (double dash) is not permitted within comments." + " And link parameters, for example ?search=... have also been REMOVED from the comment," + " because they are not readable for humans."; svg = svg.replace(TAG_G_SVG, LINE_SEPARATOR + "" + TAG_G_SVG); } if (plantUml.contains(FORBIDDEN_SEQUENCE_FOR_XML_DOCUMENT)) { formatSvg = false; } if (formatSvg) { return formatSvg(svg); } else { return svg; } } catch (Exception e) { throw new PlantumlRuntimeException("PlantUML: " + plantUml, e); } } /** *

* Call the {@link #generateSvgFromPlantUml(boolean, String, boolean)} with first parameter 'false'. *

* For launching of the generator, the Graphviz tool should be installed, * see http://plantuml.com/graphviz-dot. *

* If the plantUml notations do not begin with @startuml tag, * attach the tag to the beginning of the plantUml notations. *

* If the plantUml notations do not ends by @enduml tag, * append the tag to the ent of plantUml *

* Append commented plantUml to the end of SVG * * @param plantUml source string, for example

{@code Bob -> Alice : hello\nAlice -> Bob : hi}
* @param formatSvg if 'true', a formatted SVG content will be returned * by calling the {@link #formatSvg(String)} method, * but in case when the source plantUml content has * the [# sequence, do NOT format the SVG content. * @return SVG content. */ public String generateSvgFromPlantUml(String plantUml, boolean formatSvg) { return generateSvgFromPlantUml(false, plantUml, formatSvg); } /** * Create a formatted svg content from a source * @param svg the source * @return the formatted content */ private String formatSvg(String svg) { try { TransformerFactory transformerFactory = TransformerFactory.newInstance(); transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); StreamResult result = new StreamResult(new StringWriter()); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); DocumentBuilder db = dbf.newDocumentBuilder(); InputSource inputSource = new InputSource(new StringReader(svg)); Document document = db.parse(inputSource); DOMSource source = new DOMSource(document); transformer.transform(source, result); String formattedSvg = result.getWriter().toString(); if (logger.isTraceEnabled()) { logger.trace(formattedSvg); } return formattedSvg; } catch (Exception e) { throw new PlantumlRuntimeException("Cannot format the svg source. Source svg: " + svg, e); } } /** * Replace -- to - -, because SAXParseException: The string "--" is not permitted within comments. *

* Remove link parameters ?search=... because they are not readable for humans. * * @param plantUml PlantUML notations * @return PlantUML notations prepared for printing as a HTML comment. */ private String escape(String plantUml) { // PlantUML do the same when attaching its source to SVG xml as comment return plantUml .replace("--", "- -") .replaceAll("\\?search=.*\\s", " "); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy