diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fc2c138 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.java] +indent_size = 4 + +[*.md] +max_line_length = off +trim_trailing_whitespace = false + +[*.puml] +insert_final_newline = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.vscode/settings.json b/.vscode/settings.json index a78440a..691fda1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,21 +2,23 @@ "java.configuration.updateBuildConfiguration": "automatic", "cSpell.words": [ "Arnaud", + "buildx", "ditaa", "endditaa", "enduml", "epstext", "etag", + "ghaction", "Lalloni", + "monaco", "plantuml", "Roques", "servlet", "servlets", "startditaa", "startuml", - "utxt", - "ghaction", - "buildx" + "undock", + "utxt" ], "cSpell.allowCompoundWords": true } \ No newline at end of file diff --git a/pom.jdk8.xml b/pom.jdk8.xml index 6a8c881..37c595d 100644 --- a/pom.jdk8.xml +++ b/pom.jdk8.xml @@ -56,7 +56,7 @@ 1.2023.6 11.0.7 - 5.63.0 + 0.36.1 1.2 @@ -71,7 +71,9 @@ 4.13.2 - 2.53.0 + 4.8.3 + 5.3.2 + 2.11.0 ${jetty.version} @@ -106,8 +108,8 @@ org.webjars.npm - codemirror - ${codemirror.version} + monaco-editor + ${monaco-editor.version} jakarta.servlet @@ -175,9 +177,21 @@ test - net.sourceforge.htmlunit - htmlunit - ${htmlunit.version} + org.seleniumhq.selenium + selenium-java + ${selenium.version} + test + + + io.github.bonigarcia + webdrivermanager + ${selenium-webdrivermanager.version} + test + + + commons-io + commons-io + ${commons-io.version} test @@ -365,9 +379,9 @@ org.webjars.npm - codemirror - ${codemirror.version} - **/lib/*.js,**/lib/*.css + monaco-editor + ${monaco-editor.version} + **/min/vs/loader.js,**/min/vs/**/*,**/min-maps/vs/**/* ${project.build.outputDirectory} diff --git a/pom.xml b/pom.xml index d961fd1..af2be3e 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ 1.2023.6 11.0.7 - 5.63.0 + 0.36.1 1.2 @@ -71,7 +71,9 @@ 4.13.2 - 2.53.0 + 4.8.3 + 5.3.2 + 2.11.0 ${jetty.version} @@ -106,8 +108,8 @@ org.webjars.npm - codemirror - ${codemirror.version} + monaco-editor + ${monaco-editor.version} org.eclipse.jetty @@ -153,9 +155,21 @@ test - net.sourceforge.htmlunit - htmlunit - ${htmlunit.version} + org.seleniumhq.selenium + selenium-java + ${selenium.version} + test + + + io.github.bonigarcia + webdrivermanager + ${selenium-webdrivermanager.version} + test + + + commons-io + commons-io + ${commons-io.version} test @@ -346,9 +360,9 @@ org.webjars.npm - codemirror - ${codemirror.version} - **/lib/*.js,**/lib/*.css + monaco-editor + ${monaco-editor.version} + **/min/vs/loader.js,**/min/vs/**/*,**/min-maps/vs/**/* ${project.build.outputDirectory} diff --git a/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java b/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java index 123067e..b744f3f 100644 --- a/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java +++ b/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlServlet.java @@ -36,6 +36,7 @@ import jakarta.servlet.http.HttpServletResponse; import net.sourceforge.plantuml.OptionFlags; import net.sourceforge.plantuml.api.PlantumlUtils; +import net.sourceforge.plantuml.code.NoPlantumlCompressionException; import net.sourceforge.plantuml.png.MetadataTag; import net.sourceforge.plantuml.servlet.utility.Configuration; import net.sourceforge.plantuml.servlet.utility.UmlExtractor; @@ -134,8 +135,15 @@ public class PlantUmlServlet extends AsciiCoderServlet { final int idx = UrlDataExtractor.getIndex(request.getRequestURI()); // forward to index.jsp + final String path; + final String view = request.getParameter("view"); + if (view != null && view.equalsIgnoreCase("previewer")) { + path = "/previewer.jsp"; + } else { + path = "/index.jsp"; + } prepareRequestForDispatch(request, text, idx); - final RequestDispatcher dispatcher = request.getRequestDispatcher("/index.jsp"); + final RequestDispatcher dispatcher = request.getRequestDispatcher(path); dispatcher.forward(request, response); } @@ -183,6 +191,10 @@ public class PlantUmlServlet extends AsciiCoderServlet { if (text != null && !text.isEmpty()) { return text; } + } catch (NoPlantumlCompressionException e) { + // no textual diagram source available from Url + // ignore and try 2. method (metadata) below + // do not spam output console } catch (Exception e) { e.printStackTrace(); } @@ -229,26 +241,16 @@ public class PlantUmlServlet extends AsciiCoderServlet { */ private void prepareRequestForDispatch(HttpServletRequest request, String text, int idx) throws IOException { final String encoded = getTranscoder().encode(text); - final String index = (idx < 0) ? "" : idx + "/"; // diagram sources + request.setAttribute("encoded", encoded); request.setAttribute("decoded", text); - request.setAttribute("index", idx); + request.setAttribute("index", (idx < 0) ? "" : idx); // properties request.setAttribute("showSocialButtons", Configuration.get("SHOW_SOCIAL_BUTTONS")); request.setAttribute("showGithubRibbon", Configuration.get("SHOW_GITHUB_RIBBON")); - // image URLs - final boolean hasImg = !text.isEmpty(); - request.setAttribute("hasImg", hasImg); - request.setAttribute("imgurl", "png/" + index + encoded); - request.setAttribute("svgurl", "svg/" + index + encoded); - request.setAttribute("pdfurl", "pdf/" + index + encoded); - request.setAttribute("txturl", "txt/" + index + encoded); - request.setAttribute("mapurl", "map/" + index + encoded); // map for diagram source if necessary - final boolean hasMap = PlantumlUtils.hasCMapData(text); - request.setAttribute("hasMap", hasMap); String map = ""; - if (hasMap) { + if (PlantumlUtils.hasCMapData(text)) { try { map = UmlExtractor.extractMap(text); } catch (Exception e) { diff --git a/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlUIHelperServlet.java b/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlUIHelperServlet.java new file mode 100644 index 0000000..d3bc5be --- /dev/null +++ b/src/main/java/net/sourceforge/plantuml/servlet/PlantUmlUIHelperServlet.java @@ -0,0 +1,174 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * Project Info: https://plantuml.com + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +package net.sourceforge.plantuml.servlet; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import net.sourceforge.plantuml.theme.ThemeUtils; +import net.sourceforge.plantuml.openiconic.data.DummyIcon; + +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * Small PlantUML frontend or UI helper. + */ +@SuppressWarnings("SERIAL") +public class PlantUmlUIHelperServlet extends HttpServlet { + + private interface HelperConsumer { + void accept(HttpServletRequest request, HttpServletResponse response) throws IOException; + } + + private final Map helpers = new HashMap<>(); + private String svgIconsSpriteCache = null; + + public PlantUmlUIHelperServlet() { + // add all supported request items/helper methods + helpers.put("icons.svg", this::sendIconsSprite); + helpers.put("icons", this::sendIcons); + helpers.put("themes", this::sendThemes); + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + request.setCharacterEncoding("UTF-8"); + + final String requestItem = request.getParameter("request"); + final HelperConsumer requestHelper = this.helpers.get(requestItem); + String errorMsg = null; + if (requestItem == null) { + errorMsg = "Request item not set."; + } else if (requestHelper == null) { + errorMsg = "Unknown requested item: " + requestItem; + } + if (errorMsg != null) { + response.addHeader("Access-Control-Allow-Origin", "*"); + response.setContentType("text/plain;charset=UTF-8"); + response.getWriter().write(errorMsg); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + requestHelper.accept(request, response); + } + + private String toJson(List list) { + return "[" + list.stream() + .map(item -> "\"" + item.replace("\"", "\\\"") + "\"") + .collect(Collectors.joining(",")) + "]"; + } + + private void sendJson(HttpServletResponse response, String json) throws IOException { + response.addHeader("Access-Control-Allow-Origin", "*"); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write(json); + } + + private List getIcons() throws IOException { + InputStream in = DummyIcon.class.getResourceAsStream("all.txt"); + try (BufferedReader br = new BufferedReader(new InputStreamReader(in))) { + return br.lines().collect(Collectors.toList()); + } + } + + private void sendIconsSprite(HttpServletRequest request, HttpServletResponse response) throws IOException { + if (svgIconsSpriteCache == null) { + // NOTE: all icons has the following svg tag attributes: width="8" height="8" viewBox="0 0 8 8" + List iconNames = getIcons(); + StringBuilder sprite = new StringBuilder(); + sprite.append("\n"); + sprite.append("\n"); + sprite.append(" \n"); + sprite.append("\n"); + for (String name : iconNames) { + try (InputStream in = DummyIcon.class.getResourceAsStream(name + ".svg")) { + DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = db.parse(in); + Writer out = new StringWriter(); + out.write(""); + + Transformer tf = TransformerFactory.newInstance().newTransformer(); + tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + tf.setOutputProperty(OutputKeys.INDENT, "no"); + NodeList svgInnerNodes = doc.getElementsByTagName("svg").item(0).getChildNodes(); + for (int index = 0; index < svgInnerNodes.getLength(); index++) { + tf.transform(new DOMSource(svgInnerNodes.item(index)), new StreamResult(out)); + } + + out.write(""); + sprite.append(out.toString() + "\n"); + } catch (ParserConfigurationException | SAXException | TransformerException ex) { + // skip icons which can not be parsed/read + Logger logger = Logger.getLogger("com.plantuml"); + logger.log(Level.WARNING, "SVG icon '{0}' could not be parsed. Skip!", name); + } + } + sprite.append("\n"); + svgIconsSpriteCache = sprite.toString(); + } + response.addHeader("Access-Control-Allow-Origin", "*"); + response.setContentType("image/svg+xml;charset=UTF-8"); + response.getWriter().write(svgIconsSpriteCache); + } + + private void sendIcons(HttpServletRequest request, HttpServletResponse response) throws IOException { + sendJson(response, toJson(getIcons())); + } + + private void sendThemes(HttpServletRequest request, HttpServletResponse response) throws IOException { + sendJson(response, toJson(ThemeUtils.getAllThemeNames())); + } +} diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 9d5ea77..bb69e2f 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -1,237 +1,246 @@ - - - - - - - - - PlantUML - PlantUML Online Server - - - - - - - 120 - - - - - - - - - - - - - - - - - - - org.eclipse.jetty.servlet.Default.welcomeServlets - exact - - - - - - - - - jsp - org.eclipse.jetty.jsp.JettyJspServlet - - compilerSourceVM - 1.7 - - - compilerTargetVM - 1.7 - - - - - plantumlservlet - net.sourceforge.plantuml.servlet.PlantUmlServlet - - - plantumlservlet - /welcome - - - plantumlservlet - /uml/* - - - plantumlservlet - /form - - - plantumlservlet - /start/* - - - - imgservlet - net.sourceforge.plantuml.servlet.ImgServlet - - - imgservlet - /png/* - - - imgservlet - /img/* - - - - svgservlet - net.sourceforge.plantuml.servlet.SvgServlet - - - svgservlet - /svg/* - - - - pdfservlet - net.sourceforge.plantuml.servlet.PdfServlet - - - pdfservlet - /pdf/* - - - - epsservlet - net.sourceforge.plantuml.servlet.EpsServlet - - - epsservlet - /eps/* - - - - epstextservlet - net.sourceforge.plantuml.servlet.EpsTextServlet - - - epstextservlet - /epstext/* - - - - base64servlet - net.sourceforge.plantuml.servlet.Base64Servlet - - - base64servlet - /base64/* - - - - asciiservlet - net.sourceforge.plantuml.servlet.AsciiServlet - - - asciiservlet - /txt/* - - - - proxyservlet - net.sourceforge.plantuml.servlet.ProxyServlet - - - proxyservlet - /proxy - - - - oldproxyservlet - net.sourceforge.plantuml.servlet.OldProxyServlet - - - oldproxyservlet - /proxy/* - - - - mapservlet - net.sourceforge.plantuml.servlet.MapServlet - - - mapservlet - /map/* - - - - checkservlet - net.sourceforge.plantuml.servlet.CheckSyntaxServlet - - - checkservlet - /check/* - - - - languageservlet - net.sourceforge.plantuml.servlet.LanguageServlet - - - languageservlet - /language - - - - asciicoder - net.sourceforge.plantuml.servlet.AsciiCoderServlet - - - asciicoder - /coder/* - - - - - - - - - java.lang.Throwable - /error.jsp - - - - 500 - /error.jsp - - - - - - - - - welcome - - - + + + + + + + + + PlantUML + PlantUML Online Server + + + + + + + 120 + + + + + + + + + + + + + + + + + + + org.eclipse.jetty.servlet.Default.welcomeServlets + exact + + + + + + + + + jsp + org.eclipse.jetty.jsp.JettyJspServlet + + compilerSourceVM + 1.8 + + + compilerTargetVM + 1.8 + + + + + plantumlservlet + net.sourceforge.plantuml.servlet.PlantUmlServlet + + + plantumlservlet + /welcome + + + plantumlservlet + /uml/* + + + plantumlservlet + /form + + + plantumlservlet + /start/* + + + + imgservlet + net.sourceforge.plantuml.servlet.ImgServlet + + + imgservlet + /png/* + + + imgservlet + /img/* + + + + svgservlet + net.sourceforge.plantuml.servlet.SvgServlet + + + svgservlet + /svg/* + + + + pdfservlet + net.sourceforge.plantuml.servlet.PdfServlet + + + pdfservlet + /pdf/* + + + + epsservlet + net.sourceforge.plantuml.servlet.EpsServlet + + + epsservlet + /eps/* + + + + epstextservlet + net.sourceforge.plantuml.servlet.EpsTextServlet + + + epstextservlet + /epstext/* + + + + base64servlet + net.sourceforge.plantuml.servlet.Base64Servlet + + + base64servlet + /base64/* + + + + asciiservlet + net.sourceforge.plantuml.servlet.AsciiServlet + + + asciiservlet + /txt/* + + + + proxyservlet + net.sourceforge.plantuml.servlet.ProxyServlet + + + proxyservlet + /proxy + + + + oldproxyservlet + net.sourceforge.plantuml.servlet.OldProxyServlet + + + oldproxyservlet + /proxy/* + + + + mapservlet + net.sourceforge.plantuml.servlet.MapServlet + + + mapservlet + /map/* + + + + checkservlet + net.sourceforge.plantuml.servlet.CheckSyntaxServlet + + + checkservlet + /check/* + + + + languageservlet + net.sourceforge.plantuml.servlet.LanguageServlet + + + languageservlet + /language + + + + asciicoderservlet + net.sourceforge.plantuml.servlet.AsciiCoderServlet + + + asciicoderservlet + /coder/* + + + + plantumluihelperservlet + net.sourceforge.plantuml.servlet.PlantUmlUIHelperServlet + + + plantumluihelperservlet + /ui-helper/* + + + + + + + + + java.lang.Throwable + /error.jsp + + + + 500 + /error.jsp + + + + + + + + + welcome + + + diff --git a/src/main/webapp/assets/copy.svg b/src/main/webapp/assets/copy.svg new file mode 100644 index 0000000..17e128a --- /dev/null +++ b/src/main/webapp/assets/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/webapp/assets/dock.svg b/src/main/webapp/assets/dock.svg new file mode 100644 index 0000000..bc4aa2e --- /dev/null +++ b/src/main/webapp/assets/dock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/webapp/assets/file-types/ascii.svg b/src/main/webapp/assets/file-types/ascii.svg new file mode 100644 index 0000000..801b93d --- /dev/null +++ b/src/main/webapp/assets/file-types/ascii.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/webapp/assets/file-types/map.svg b/src/main/webapp/assets/file-types/map.svg new file mode 100644 index 0000000..edd9b8a --- /dev/null +++ b/src/main/webapp/assets/file-types/map.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/webapp/assets/file-types/pdf.svg b/src/main/webapp/assets/file-types/pdf.svg new file mode 100644 index 0000000..e718679 --- /dev/null +++ b/src/main/webapp/assets/file-types/pdf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/webapp/assets/file-types/png.svg b/src/main/webapp/assets/file-types/png.svg new file mode 100644 index 0000000..1553a22 --- /dev/null +++ b/src/main/webapp/assets/file-types/png.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/webapp/assets/file-types/svg.svg b/src/main/webapp/assets/file-types/svg.svg new file mode 100644 index 0000000..7d2670d --- /dev/null +++ b/src/main/webapp/assets/file-types/svg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/webapp/assets/file-types/txt.svg b/src/main/webapp/assets/file-types/txt.svg new file mode 100644 index 0000000..4623344 --- /dev/null +++ b/src/main/webapp/assets/file-types/txt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/webapp/assets/github-fork-me.png b/src/main/webapp/assets/github-fork-me.png new file mode 100644 index 0000000..10c08f4 Binary files /dev/null and b/src/main/webapp/assets/github-fork-me.png differ diff --git a/src/main/webapp/assets/settings.svg b/src/main/webapp/assets/settings.svg new file mode 100644 index 0000000..7362425 --- /dev/null +++ b/src/main/webapp/assets/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/webapp/assets/undock.svg b/src/main/webapp/assets/undock.svg new file mode 100644 index 0000000..89d2bbb --- /dev/null +++ b/src/main/webapp/assets/undock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/webapp/footer.jspf b/src/main/webapp/footer.jspf deleted file mode 100644 index 509d089..0000000 --- a/src/main/webapp/footer.jspf +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp index 4da6ec8..265c90a 100644 --- a/src/main/webapp/index.jsp +++ b/src/main/webapp/index.jsp @@ -1,93 +1,57 @@ <%@ page info="index" contentType="text/html; charset=utf-8" pageEncoding="utf-8" session="false" %> - <% - // diagram sources - String decoded = request.getAttribute("decoded").toString(); - // properties - boolean showSocialButtons = (boolean)request.getAttribute("showSocialButtons"); - boolean showGithubRibbon = (boolean)request.getAttribute("showGithubRibbon"); - // image URLs - boolean hasImg = (boolean)request.getAttribute("hasImg"); - String imgurl = request.getAttribute("imgurl").toString(); - String svgurl = request.getAttribute("svgurl").toString(); - String txturl = request.getAttribute("txturl").toString(); - String pdfurl = request.getAttribute("pdfurl").toString(); - String mapurl = request.getAttribute("mapurl").toString(); - // map for diagram source if necessary - boolean hasMap = (boolean)request.getAttribute("hasMap"); - String map = request.getAttribute("map").toString(); + // diagram sources + String encoded = request.getAttribute("encoded").toString(); + String decoded = request.getAttribute("decoded").toString(); + String index = request.getAttribute("index").toString(); + String diagramUrl = ((index.isEmpty()) ? "" : index + "/") + encoded; + // map for diagram source if necessary + String map = request.getAttribute("map").toString(); + boolean hasMap = !map.isEmpty(); + // properties + boolean showSocialButtons = (boolean)request.getAttribute("showSocialButtons"); + boolean showGithubRibbon = (boolean)request.getAttribute("showGithubRibbon"); %> - + - - - - - - - - - - - - PlantUMLServer + <%@ include file="resource/htmlheadbase.jsp" %> + PlantUML Server -