/*
* Copyright 2016 Igor Maznitsa.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.igormaznitsa.mindmap.plugins.importers;
import java.awt.Color;
import java.awt.Image;
import com.igormaznitsa.mindmap.plugins.api.AbstractImporter;
import java.io.File;
import java.io.FileInputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.igormaznitsa.meta.annotation.MustNotContainNull;
import com.igormaznitsa.meta.common.utils.Assertions;
import com.igormaznitsa.mindmap.model.ExtraLink;
import com.igormaznitsa.mindmap.model.ExtraNote;
import com.igormaznitsa.mindmap.model.MMapURI;
import com.igormaznitsa.mindmap.model.MindMap;
import com.igormaznitsa.mindmap.model.Topic;
import com.igormaznitsa.mindmap.swing.panel.DialogProvider;
import com.igormaznitsa.mindmap.swing.panel.MindMapPanel;
import com.igormaznitsa.mindmap.swing.panel.Texts;
import com.igormaznitsa.mindmap.swing.services.IconID;
import com.igormaznitsa.mindmap.swing.services.ImageIconServiceProvider;
import com.igormaznitsa.mindmap.model.logger.Logger;
import com.igormaznitsa.mindmap.model.logger.LoggerFactory;
import com.igormaznitsa.mindmap.plugins.attributes.images.ImageVisualAttributePlugin;
import com.igormaznitsa.mindmap.swing.panel.StandardTopicAttribute;
import com.igormaznitsa.mindmap.swing.panel.ui.AbstractCollapsableElement;
import com.igormaznitsa.mindmap.swing.panel.utils.MindMapUtils;
import com.igormaznitsa.mindmap.swing.panel.utils.Utils;
public class CoggleMM2MindMapImporter extends AbstractImporter {
private static final Icon ICO = ImageIconServiceProvider.findInstance().getIconForId(IconID.POPUP_IMPORT_COGGLE2MM);
private static final Logger LOGGER = LoggerFactory.getLogger(CoggleMM2MindMapImporter.class);
@Override
@Nullable
public MindMap doImport(@Nonnull final MindMapPanel panel, @Nonnull final DialogProvider dialogProvider, @Nullable final Topic actionTopic, @Nonnull @MustNotContainNull final Topic[] selectedTopics) throws Exception {
final File file = this.selectFileForExtension(panel, Texts.getString("MMDImporters.CoggleMM2MindMap.openDialogTitle"), "mm", "Coggle MM files (.MM)", Texts.getString("MMDImporters.ApproveImport"));
if (file == null) {
return null;
}
final Document document = Utils.loadXmlDocument(new FileInputStream(file), "UTF-8", true);
final MindMap result = new MindMap(null, true);
Assertions.assertNotNull(result.getRoot()).setText("Empty");
final Element root = document.getDocumentElement();
if ("map".equals(root.getTagName())) {
final List<Element> nodes = Utils.findDirectChildrenForName(root, "node");
if (!nodes.isEmpty()) {
parseTopic(result, null, result.getRoot(), nodes.get(0));
}
} else {
throw new IllegalArgumentException("File is not Coggle mind map");
}
return result;
}
private static final Pattern MD_IMAGE_LINK = Pattern.compile("\\!\\[(.*?)\\]\\((.*?)\\)", Pattern.MULTILINE | Pattern.UNICODE_CASE);
private static final Pattern MD_URL_LINK = Pattern.compile("(?<!\\!)\\[(.*?)\\]\\((.*?)\\)", Pattern.MULTILINE | Pattern.UNICODE_CASE);
@Nonnull
@MustNotContainNull
private List<String> extractImageURLs(@Nonnull final String mdText, @Nonnull final StringBuilder resultText) {
final List<String> result = new ArrayList<String>();
final Matcher matcher = MD_IMAGE_LINK.matcher(mdText);
int lastFoundEnd = 0;
while (matcher.find()) {
final String text = matcher.group(1);
result.add(matcher.group(2));
resultText.append(mdText, lastFoundEnd, matcher.start()).append(text);
lastFoundEnd = matcher.end();
}
if (lastFoundEnd < mdText.length()) {
resultText.append(mdText, lastFoundEnd, mdText.length());
}
return result;
}
@Nonnull
@MustNotContainNull
private List<String> extractURLs(@Nonnull final String mdText, @Nonnull final StringBuilder resultText) {
final List<String> result = new ArrayList<String>();
final Matcher matcher = MD_URL_LINK.matcher(mdText);
int lastFoundEnd = 0;
while (matcher.find()) {
final String text = matcher.group(1);
result.add(matcher.group(2));
resultText.append(mdText, lastFoundEnd, matcher.start()).append(text);
lastFoundEnd = matcher.end();
}
if (lastFoundEnd < mdText.length()) {
resultText.append(mdText, lastFoundEnd, mdText.length());
}
return result;
}
@Nullable
private static String loadImageForURLAndEncode(@Nonnull final String imageUrl) {
String result = null;
final Image loadedImage;
try {
loadedImage = ImageIO.read(new URL(imageUrl));
}
catch (final Exception ex) {
LOGGER.error("Can't load image for URL : " + imageUrl, ex);
return null;
}
if (loadedImage != null) {
try {
result = Utils.rescaleImageAndEncodeAsBase64(loadedImage, -1);
}
catch (final Exception ex) {
LOGGER.error("Can't decode image", ex);
}
}
return result;
}
@Nullable
private static String loadFirstSuccessfulImage(@Nonnull @MustNotContainNull final List<String> urls) {
String result = null;
for (final String url : urls) {
result = loadImageForURLAndEncode(url);
if (result != null) {
break;
}
}
return result;
}
@Nullable
private static MMapURI getFirstSuccessfulURL(@Nonnull @MustNotContainNull final List<String> urls) {
MMapURI result = null;
for (final String url : urls) {
try {
result = new MMapURI(url);
}
catch (final Exception ex) {
LOGGER.error("Can't recognize URI : " + url, ex);
}
if (result != null) {
break;
}
}
return result;
}
private void parseTopic(@Nonnull final MindMap map, @Nullable final Topic parent, @Nullable final Topic preGeneratedOne, @Nonnull final Element element) {
final Topic topicToProcess;
if (preGeneratedOne == null) {
topicToProcess = Assertions.assertNotNull(parent).makeChild("", null);
} else {
topicToProcess = preGeneratedOne;
}
final StringBuilder resultTextBuffer = new StringBuilder();
final List<String> foundImageURLs = extractImageURLs(element.getAttribute("TEXT"), resultTextBuffer);
String nodeText = resultTextBuffer.toString();
resultTextBuffer.setLength(0);
final List<String> foundLinkURLs = extractURLs(nodeText, resultTextBuffer);
final MMapURI succesfullDecodedUrl = getFirstSuccessfulURL(foundLinkURLs);
nodeText = resultTextBuffer.toString();
final String encodedImage = loadFirstSuccessfulImage(foundImageURLs);
if (encodedImage != null) {
topicToProcess.setAttribute(ImageVisualAttributePlugin.ATTR_KEY, encodedImage);
}
if (succesfullDecodedUrl != null) {
topicToProcess.setExtra(new ExtraLink(succesfullDecodedUrl));
}
final StringBuilder note = new StringBuilder();
if (!foundLinkURLs.isEmpty() && (succesfullDecodedUrl == null || foundLinkURLs.size() > 1)) {
if (note.length() > 0) {
note.append("\n\n");
}
note.append("Detected URLs\n---------------");
for (final String u : foundLinkURLs) {
note.append('\n').append(u);
}
}
if (!foundImageURLs.isEmpty() && (encodedImage == null || foundImageURLs.size() > 1)) {
if (note.length() > 0) {
note.append("\n\n");
}
note.append("Detected image links\n---------------");
for (final String u : foundImageURLs) {
note.append('\n').append(u);
}
}
final String text = nodeText.replace("\r", "");
final String position = element.getAttribute("POSITION");
final String folded = element.getAttribute("FOLDED");
Color edgeColor = null;
for (final Element e : Utils.findDirectChildrenForName(element, "edge")) {
try {
edgeColor = Utils.html2color(e.getAttribute("COLOR"), false);
}
catch (final Exception ex) {
LOGGER.error("Can't parse color value", ex);
}
}
topicToProcess.setText(text);
if (parent != null && parent.isRoot() && "left".equalsIgnoreCase(position)) {
AbstractCollapsableElement.makeTopicLeftSided(topicToProcess, true);
}
if ("true".equalsIgnoreCase(folded)) {
MindMapUtils.setCollapsed(topicToProcess, true);
}
if (edgeColor != null) {
topicToProcess.setAttribute(StandardTopicAttribute.ATTR_FILL_COLOR.getText(), Utils.color2html(edgeColor, false));
topicToProcess.setAttribute(StandardTopicAttribute.ATTR_TEXT_COLOR.getText(), Utils.color2html(Utils.makeContrastColor(edgeColor), false));
}
if (note.length() > 0) {
topicToProcess.setExtra(new ExtraNote(note.toString()));
}
for (final Element c : Utils.findDirectChildrenForName(element, "node")) {
parseTopic(map, topicToProcess, null, c);
}
}
@Override
@Nullable
public String getMnemonic() {
return "cogglemm";
}
@Override
@Nonnull
public String getName(@Nonnull final MindMapPanel panel, @Nullable final Topic actionTopic, @Nonnull @MustNotContainNull final Topic[] selectedTopics) {
return Texts.getString("MMDImporters.CoggleMM2MindMap.Name");
}
@Override
@Nonnull
public String getReference(@Nonnull final MindMapPanel panel, @Nullable final Topic actionTopic, @Nonnull @MustNotContainNull final Topic[] selectedTopics) {
return Texts.getString("MMDImporters.CoggleMM2MindMap.Reference");
}
@Override
@Nonnull
public Icon getIcon(@Nonnull final MindMapPanel panel, @Nullable final Topic actionTopic, @Nonnull @MustNotContainNull final Topic[] selectedTopics) {
return ICO;
}
@Override
public int getOrder() {
return 5;
}
}