/**
* 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;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;
import com.rapidminer.gui.renderer.RendererService;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.ports.InputPort;
import com.rapidminer.operator.ports.Port;
import com.rapidminer.operator.ports.Ports;
import com.rapidminer.operator.ports.metadata.MetaData;
import com.rapidminer.operator.ports.metadata.Precondition;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.Parameters;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.Tools;
import com.rapidminer.tools.WebServiceTools;
import com.rapidminer.tools.documentation.ExampleProcess;
/**
* This class loads the operator's descriptions either live from the internet wiki or from the
* resources. The latter requires that a custom Bot which gets all operator description sites from
* the MediaWiki was executed during build time. Those html files retrieved there for an operator
* must have been parsed and saved in the resources folder in "doc/namespace/operatorname". If the
* user does not have an internet connection all operators are loaded from this resource. If the
* user does have an internet connection the operators are loaded directly from the RapidWiki site.
* The operator description is shown in the RapidMiner Help Window.
*
* @author Miguel Buescher, Sebastian Land, Marcel Seifert
*
*/
public class OperatorDocLoader {
/**
* The documentation cache. It is used to cache documentations after reading them for the first
* time.
*/
private static Map<String, String> DOC_CACHE = new HashMap<>(100);
/**
* Gets the operator documentation as an HTML string.
*
* The documentation sources will be used in the following order: 1. Documentation Cache 2.
* Single-XML documentation format (for each operator) 3. OperatorsDoc.xml file documentation
* format
*
* Previously uncached documentations will be cached after reading for a better response time.
* If something goes wrong, a log message will be triggered.
*
* @param operator
* The operator from that to get the documentation.
* @return HTML string of the operator documentation
*/
static String getDocumentation(Operator operator) {
String html = null;
if (operator != null) {
// load cache
String key = operator.getOperatorDescription().getKey();
if (DOC_CACHE.containsKey(key)) {
return DOC_CACHE.get(key);
}
// load XML
URL resourceURL = OperatorDocumentationBrowser.getDocResourcePath(operator);
if (resourceURL != null) {
try (InputStream xmlStream = WebServiceTools.openStreamFromURL(resourceURL)) {
Source xmlSource = new StreamSource(xmlStream);
if (xmlSource != null) {
html = OperatorDocToHtmlConverter.applyXSLTTransformation(xmlSource);
if (html != null) {
html = html.replace("xmlns:rmdoc=\"com.rapidminer.gui.OperatorDocumentationBrowser\"", " ");
}
}
} catch (IOException | TransformerException e) {
LogService.getRoot().log(Level.INFO, "com.rapidminer.gui.OperatorDocLoader.xml_error", e);
}
}
// load operatorsDoc
if (html == null) {
html = makeOperatorDocumentation(operator);
if (html == null) {
LogService.getRoot().log(Level.INFO, "com.rapidminer.gui.OperatorDocLoader.operatorsdoc_error");
}
}
// write cache
if (html != null) {
DOC_CACHE.put(key, html);
return html;
}
}
LogService.getRoot().log(Level.INFO, "com.rapidminer.gui.OperatorDocLoader.operator_not_found");
return getErrorText(operator);
}
/**
* This generates the operator documentation from the operatorsDoc
*/
private static String makeOperatorDocumentation(Operator displayedOperator) {
OperatorDescription descr = displayedOperator.getOperatorDescription();
StringBuilder buf = new StringBuilder(2048);
buf.append("<html><body><table><tr><td>");
String iconName = "icons/24/" + displayedOperator.getOperatorDescription().getIconName();
URL resource = Tools.getResource(iconName);
if (resource != null) {
buf.append("<img src=\"");
buf.append(resource);
buf.append("\" class=\"HeadIcon\"/> ");
}
buf.append("<td valign=\"middle\" align=\"left\"> <h2 class=\"firstHeading\" id=\"firstHeading\">");
buf.append(descr.getName());
buf.append("<span class=\"packageName\"><br/>");
buf.append(descr.getProviderName());
buf.append("</span></h2></td></tr></table><div style=\"border-top: 1px solid #bbbbbb\">");
buf.append(OperatorDocToHtmlConverter.getTagHtmlForDescription(descr));
buf.append("<h4>Synopsis</h4><p>");
buf.append(descr.getShortDescription());
buf.append("</p></p><br/><h4>Description</h4>");
String descriptionText = descr.getLongDescriptionHTML();
if (descriptionText != null) {
if (!descriptionText.trim().startsWith("<p>")) {
buf.append("<p>");
}
buf.append(descriptionText);
if (!descriptionText.trim().endsWith("</p>")) {
buf.append("</p>");
}
buf.append("<br/>");
}
appendPortsToDocumentation(displayedOperator.getInputPorts(), "Input", buf);
appendPortsToDocumentation(displayedOperator.getOutputPorts(), "Output", buf);
Parameters parameters = displayedOperator.getParameters();
if (parameters.getKeys().size() > 0) {
buf.append("<h4 class=\"parametersHeading\">Parameters</h4><table class=\"parametersTable\">");
for (String key : parameters.getKeys()) {
ParameterType type = parameters.getParameterType(key);
if (type == null) {
LogService.getRoot().log(Level.WARNING, "com.rapidminer.gui.OperatorDocLoader.unkwown_parameter_key",
new Object[] { displayedOperator.getName(), key });
continue;
}
buf.append("<tr><td><b>");
buf.append(makeParameterHeader(type));
buf.append("</b>");
if (type.isOptional()) {
buf.append(" (optional)");
}
buf.append("</td></tr><tr><td>");
buf.append(type.getDescription());
buf.append("</td></tr><tr><td class=\"parameterDetailsCell\"><span class=\"parameterDetails\">");
String parameterType = OperatorDocToHtmlConverter.getParameterType(descr.getKey(), key);
buf.append("<b>Type: </b> <i>");
buf.append(parameterType);
buf.append("</i>");
if (parameterType.equals(OperatorDocToHtmlConverter.REAL_LABEL)
|| parameterType.equals(OperatorDocToHtmlConverter.INTEGER_LABEL)
|| parameterType.equals(OperatorDocToHtmlConverter.LONG_LABEL)
|| parameterType.equals(OperatorDocToHtmlConverter.SELECTION_LABEL)) {
buf.append("<br/><b>Range: </b> <i>");
buf.append(OperatorDocToHtmlConverter.getParameterRange(descr.getKey(), key));
buf.append("</i>");
}
String parameterDefault = OperatorDocToHtmlConverter.getParameterDefault(descr.getKey(), key);
if (!parameterDefault.trim().isEmpty()) {
buf.append("<br/><b>Default: </b> <i>");
buf.append(OperatorDocToHtmlConverter.getParameterDefault(descr.getKey(), key));
buf.append("</i>");
}
buf.append("</span></td></tr>");
}
buf.append("</table>");
}
if (!descr.getOperatorDocumentation().getExamples().isEmpty()) {
buf.append("<h4>Examples</h4><ul>");
int i = 0;
for (ExampleProcess exampleProcess : descr.getOperatorDocumentation().getExamples()) {
buf.append("<li>");
buf.append(exampleProcess.getComment());
buf.append(makeExampleFooter(i));
buf.append("</li>");
i++;
}
buf.append("</ul>");
}
buf.append("</div></body></html>");
return buf.toString();
}
private static Object makeExampleFooter(int exampleIndex) {
return "<br/><a href=\"show_example_" + exampleIndex + "\">Show example process</a>.";
}
private static void appendPortsToDocumentation(Ports<? extends Port> ports, String title, StringBuilder buf) {
if (ports.getNumberOfPorts() > 0) {
buf.append("<h4>" + title + "</h4><table border=\"0\" cellspacing=\"0\"><tr><td>");
for (Port port : ports.getAllPorts()) {
buf.append("<table><tr>");
buf.append("<td class=\"lilIcon\">");
Class<? extends IOObject> typeClass = null;
if (port instanceof InputPort) {
// Input Port
InputPort inputPort = (InputPort) port;
List<Precondition> preconditions = new LinkedList<Precondition>(inputPort.getAllPreconditions());
if (preconditions.size() > 0) {
MetaData metaData = preconditions.get(0).getExpectedMetaData();
if (metaData != null) {
typeClass = metaData.getObjectClass();
}
}
}
String imgSrc = getIconNameForType(typeClass);
buf.append("<img src=\"" + imgSrc + "\" class=\"typeIcon\" />");
buf.append("</td><td>");
buf.append("<b>" + port.getName() + "</b>");
if (typeClass != null) {
buf.append("<i>" + OperatorDocToHtmlConverter.getTypeNameForType(typeClass) + "</i>");
}
buf.append("</td>");
buf.append("</tr>");
buf.append("</table></td></tr>");
}
buf.append("</table><br/>");
}
}
private static String makeParameterHeader(ParameterType type) {
return type.getKey().replace('_', ' ');
}
/**
*
* Searches for a class with the given name and returns the path of the resource.
*
* @param clazz
* the class as Class.
* @return the path of the resource of the corresponding icon.
*/
private static String getIconNameForType(Class<? extends IOObject> clazz) {
String path = null;
String iconName;
if (clazz == null) {
iconName = "plug.png";
} else {
iconName = RendererService.getIconName(clazz);
}
try {
path = SwingTools.getIconPath("24/" + iconName);
} catch (Exception e) {
LogService.getRoot().finer("Error retrieving icon for type '" + clazz + "'! Reason: " + e.getLocalizedMessage());
}
return path;
}
/**
* Generates a HTML string which contains an error text for the case that an operator
* documentation was not found.
*
* @param operator
* The operator which documentation not was found
* @return An error HTML which says, that no documentation for the operator was found
*/
private static String getErrorText(Operator operator) {
String opName;
if (operator != null) {
opName = operator.getName();
} else {
opName = "unnamed";
}
StringBuilder builder = new StringBuilder();
builder.append(
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html xmlns=\"http://www.w3.org/1999/xhtml\" dir=\"ltr\" lang=\"en\" xml:lang=\"en\"><head><table cellpadding=0 cellspacing=0><tr><td><img src=\"");
builder.append(SwingTools.getIconPath("48/bug_error.png"));
builder.append("\"/></td><td width=\"5\"></td><td>");
builder.append(I18N.getErrorMessage("documentation.could_not_find", opName));
builder.append("</td></tr></table></head></html>");
return builder.toString();
}
}