package org.plantuml.idea.rendering; import org.apache.commons.lang.builder.ToStringBuilder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.plantuml.idea.util.UIUtils; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import static org.plantuml.idea.intentions.ReverseArrowIntention.logger; public class ImageItem { private final int page; @Nullable private final String description; @NotNull private final RenderingType renderingType; @Nullable private final BufferedImage image; @NotNull private final UrlData[] urls; @Nullable private final String title; @Nullable private final String pageSource; @NotNull private final String documentSource; private byte[] imageBytes; public ImageItem(@NotNull ImageItem item, @NotNull String documentSource, @Nullable String title) { this.page = item.page; this.description = item.description; this.pageSource = item.pageSource; this.image = item.image; this.urls = item.urls; this.renderingType = item.renderingType; this.documentSource = documentSource; this.title = title; } public ImageItem(@NotNull File baseDir, @NotNull String documentSource, @Nullable String pageSource, int page, @Nullable String description, @Nullable byte[] imageBytes, @Nullable byte[] svgBytes, @NotNull RenderingType renderingType, @Nullable String title) { this.pageSource = pageSource; this.documentSource = documentSource; this.page = page; this.description = description; this.renderingType = renderingType; this.title = title; this.imageBytes = imageBytes; if (this.imageBytes != null) { try { this.image = UIUtils.getBufferedImage(imageBytes); } catch (Exception e) { throw new RuntimeException(e); } } else { this.image = null; } this.urls = this.parseUrls(svgBytes, baseDir); } public ImageItem(int page, ImageItem item) { this.page = page; this.description = item.description; this.pageSource = item.pageSource; this.documentSource = item.documentSource; this.image = item.image; this.urls = item.urls; this.imageBytes = item.imageBytes; this.renderingType = item.renderingType; this.title = item.title; } @Nullable public String getTitle() { return title; } @NotNull public RenderingType getRenderingType() { return renderingType; } @Nullable public BufferedImage getImage() { return image; } @NotNull public String getDocumentSource() { return documentSource; } @Nullable public String getDescription() { return description; } public int getPage() { return page; } public boolean hasImage() { return image != null; } public byte[] getImageBytes() { return imageBytes; } @Nullable public String getPageSource() { return pageSource; } @NotNull public UrlData[] getUrls() { return urls; } protected boolean hasError() { String description = getDescription(); if (description == null || description.isEmpty() || "(Error)".equals(description)) { return true; } return false; } public class UrlData { private final URI uri; private final Rectangle clickArea; public UrlData(URI uri, Rectangle clickArea) { this.uri = uri; this.clickArea = clickArea; } public URI getUri() { return uri; } public Rectangle getClickArea() { return clickArea; } } private UrlData[] parseUrls(byte[] svgData, File baseDir) { if (svgData == null || svgData.length == 0 || baseDir == null) { return new UrlData[0]; } try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //http://stackoverflow.com/a/155874/685796 factory.setValidating(false); factory.setNamespaceAware(true); factory.setFeature("http://xml.org/sax/features/namespaces", false); factory.setFeature("http://xml.org/sax/features/validation", false); factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(new ByteArrayInputStream(svgData)); String xpathExpression = "//a"; XPathFactory xpf = XPathFactory.newInstance(); XPath xpath = xpf.newXPath(); XPathExpression expression = xpath.compile(xpathExpression); NodeList svgPaths = (NodeList) expression.evaluate(document, XPathConstants.NODESET); List<UrlData> urls = new ArrayList<UrlData>(); for (int i = 0; i < svgPaths.getLength(); i++) { urls.addAll(createUrl(svgPaths.item(i), baseDir)); } return urls.toArray(new UrlData[urls.size()]); } catch (Exception e) { logger.debug(e); return new UrlData[0]; } } private List<UrlData> createUrl(Node linkNode, File baseDir) throws URISyntaxException { List<UrlData> urls = new ArrayList<UrlData>(); URI url = this.computeUri(linkNode.getAttributes().getNamedItem("xlink:href").getNodeValue(), baseDir); for (int i = 0; i < linkNode.getChildNodes().getLength(); i++) { Node child = linkNode.getChildNodes().item(i); if (child.getNodeName().equals("rect")) { NamedNodeMap nodeAttributes = child.getAttributes(); Rectangle rect = new Rectangle( (int) Float.parseFloat(nodeAttributes.getNamedItem("x").getNodeValue()), (int) Float.parseFloat(nodeAttributes.getNamedItem("y").getNodeValue()), (int) Float.parseFloat(nodeAttributes.getNamedItem("width").getNodeValue()), (int) Float.parseFloat(nodeAttributes.getNamedItem("height").getNodeValue()) ); urls.add(new UrlData(url, rect)); } } return urls; } /** * If uri is a relative path, then assuming that full uri is file:/{path_to_diagram_file}/{uri} * * @param url absolute or relative url * @return absolute uri */ private URI computeUri(String url, File baseDir) throws URISyntaxException { URI uri = new URI(url); if (!uri.isAbsolute()) { //Concatenating baseDir and relative URI uri = new File(baseDir, url).toURI(); } return uri; } @Override public String toString() { return new ToStringBuilder(this) .append("page", page) .append("description", description) .append("title", title) .append("hasImage", hasImage()) .toString(); } }