/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program is 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 * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.flow.processrendering.annotations; import java.io.IOException; import java.io.StringWriter; import javax.swing.JEditorPane; import javax.swing.text.BadLocationException; import javax.swing.text.html.HTMLDocument; import org.apache.commons.lang.StringEscapeUtils; import com.rapidminer.RapidMiner; import com.rapidminer.gui.flow.processrendering.annotations.model.WorkflowAnnotation; import com.rapidminer.gui.flow.processrendering.annotations.style.AnnotationStyle; import com.rapidminer.tools.SystemInfoUtilities; import com.rapidminer.tools.SystemInfoUtilities.OperatingSystem; /** * Utility methods for {@link WorkflowAnnotation}s. * * @author Marco Boeck * @since 6.4.0 * */ public final class AnnotationDrawUtils { /** surrounding HTML for annotations where styling information can be set */ private static final String ANNOTATION_HTML_FORMAT = "<div id=\"anno_style_div\" style=\"font-size: 10px; font-family: 'Open Sans'; padding: %dpx; %s\">%s</div>"; /** this is removed from the beginning of the document */ private static final String ANNOTATION_HTML_FORMATTING_START_REGEX = "\\s*<html>\\s*(<head>)?\\s*.*\\s*(</head>)?\\s*<body>\\s*"; /** this is removed from the end of the document */ private static final String ANNOTATION_HTML_FORMATTING_END_REGEX = "\\s*</body>\\s*</html>\\s*"; /** divs are forbidden because we use them to style the annotation globally */ private static final String ANNOTATION_HTML_FORMATTING_MISC_DIV_START = "<div(.*?)>"; /** divs are forbidden because we use them to style the annotation globally */ private static final String ANNOTATION_HTML_FORMATTING_MISC_DIV_END = "</div>"; /** the signal we fake everytime the user enters a newline */ public static final String ANNOTATION_HTML_NEWLINE_SIGNAL = "\r"; /** * Private constructor which throws if called. */ private AnnotationDrawUtils() { throw new UnsupportedOperationException("Static utility class"); } /** * Creates a HTML document which applies the style information from the given annotation. * * @param annotation * the annotation for which to create the full HTML document * @return the HTML document as a string */ public static String createStyledCommentString(final WorkflowAnnotation annotation) { if (annotation == null) { throw new IllegalArgumentException("annotation must not be null!"); } return createStyledCommentString(annotation.getComment(), annotation.getStyle()); } /** * Creates a HTML document which applies the style information from the given annotation. * * @param comment * the comment for which to create the full HTML document * @param style * the style which should be used to style the comment * @return the HTML document as a string */ public static String createStyledCommentString(final String comment, final AnnotationStyle style) { if (comment == null) { throw new IllegalArgumentException("comment must not be null!"); } if (style == null) { throw new IllegalArgumentException("style must not be null!"); } return String.format(ANNOTATION_HTML_FORMAT, style.getPadding(), style.getAnnotationAlignment().getCSS(), comment); } /** * This method removes styling information from the HTML comment. * <p> * <strong>Attention:</strong> It is not possible to use Regex to parse arbitrary HTML!! This * will fail horribly in case the user entered complex HTML! Works fine for simple HTML without * tables etc. * </p> * * @param comment * the comment to remove styling information from * @return the sanitized comment */ public static String removeStyleFromComment(final String comment) { if (comment == null) { throw new IllegalArgumentException("comment must not be null!"); } String newComment = comment.replaceAll(ANNOTATION_HTML_FORMATTING_START_REGEX, ""); newComment = newComment.replaceAll(ANNOTATION_HTML_FORMATTING_END_REGEX, ""); // replace potential occurances of any divs newComment = newComment.replaceAll(ANNOTATION_HTML_FORMATTING_MISC_DIV_START, ""); newComment = newComment.replaceAll(ANNOTATION_HTML_FORMATTING_MISC_DIV_END, ""); // IMPORTANT: convert newline signal to HTML linebreak newComment = newComment.replaceAll(ANNOTATION_HTML_NEWLINE_SIGNAL, "<br/>"); // remove superflous whitespaces newComment = newComment.replaceAll("\n", ""); newComment = newComment.replaceAll("\\s+", " "); newComment = newComment.trim(); return newComment; } /** * Returns plain text from the editor. * * @param editor * the editor from which to take the text. * @param onlySelected * if {@code true} will only return the selected text * @return the text of the editor converted to plain text * @throws BadLocationException * @throws IOException */ public static String getPlaintextFromEditor(final JEditorPane editor, final boolean onlySelected) throws IOException, BadLocationException { if (editor == null) { throw new IllegalArgumentException("editor must not be null!"); } HTMLDocument document = (HTMLDocument) editor.getDocument(); StringWriter writer = new StringWriter(); int start = 0; int length = document.getLength(); if (onlySelected) { start = editor.getSelectionStart(); length = editor.getSelectionEnd() - start; } editor.getEditorKit().write(writer, document, start, length); String text = writer.toString(); text = AnnotationDrawUtils.removeStyleFromComment(text); // switch <br> and <br/> to actual newline (current system) text = text.replaceAll("<br.*?>", System.lineSeparator()); // kill all other html tags text = text.replaceAll("\\<.*?>", ""); text = StringEscapeUtils.unescapeHtml(text); return text; } /** * Calculates the preferred height of an editor pane with the given fixed width for the * specified string. * * @param comment * the annotation comment string * @param width * the width of the content * @return the preferred height given the comment */ public static int getContentHeight(final String comment, final int width) { if (comment == null) { throw new IllegalArgumentException("comment must not be null!"); } // do not create Swing components for headless mode if (RapidMiner.getExecutionMode().isHeadless()) { return 0; } JEditorPane dummyEditorPane = new JEditorPane("text/html", ""); dummyEditorPane.setText(comment); dummyEditorPane.setBorder(null); dummyEditorPane.setSize(width, Short.MAX_VALUE); // height is not exact. Multiply by magic number to get a more fitting value... if (SystemInfoUtilities.getOperatingSystem() == OperatingSystem.OSX || SystemInfoUtilities.getOperatingSystem() == OperatingSystem.UNIX || SystemInfoUtilities.getOperatingSystem() == OperatingSystem.SOLARIS) { return (int) (dummyEditorPane.getPreferredSize().getHeight() * 1.05f); } else { return (int) dummyEditorPane.getPreferredSize().getHeight(); } } }