/*
* Copyright 2015 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.exporters;
import com.igormaznitsa.mindmap.plugins.api.AbstractExporter;
import com.igormaznitsa.mindmap.model.Extra;
import com.igormaznitsa.mindmap.model.ExtraFile;
import com.igormaznitsa.mindmap.model.ExtraLink;
import com.igormaznitsa.mindmap.model.ExtraNote;
import com.igormaznitsa.mindmap.model.ExtraTopic;
import com.igormaznitsa.mindmap.model.MMapURI;
import com.igormaznitsa.mindmap.model.ModelUtils;
import com.igormaznitsa.mindmap.model.Topic;
import com.igormaznitsa.mindmap.swing.panel.MindMapPanel;
import com.igormaznitsa.mindmap.swing.panel.utils.Utils;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.sql.Timestamp;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.JComponent;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import com.igormaznitsa.mindmap.swing.panel.utils.MindMapUtils;
import com.igormaznitsa.mindmap.swing.panel.Texts;
import com.igormaznitsa.mindmap.swing.services.IconID;
import com.igormaznitsa.mindmap.swing.services.ImageIconServiceProvider;
import com.igormaznitsa.meta.annotation.MustNotContainNull;
import javax.swing.Icon;
import static com.igormaznitsa.meta.common.utils.Assertions.assertNotNull;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
public class MDExporter extends AbstractExporter {
private static final int STARTING_INDEX_FOR_NUMERATION = 5;
private static class State {
private static final String NEXT_LINE = System.getProperty("line.separator", "\n");//NOI18N
private final StringBuilder buffer = new StringBuilder(16384);
@Nonnull
public State append(final char ch) {
this.buffer.append(ch);
return this;
}
@Nonnull
public State nextStringMarker() {
this.buffer.append(" ");//NOI18N
return this;
}
@Nonnull
public State append(@Nonnull final String str) {
this.buffer.append(str);
return this;
}
@Nonnull
public State nextLine() {
this.buffer.append(NEXT_LINE);
return this;
}
@Override
@Nonnull
public String toString() {
return this.buffer.toString();
}
}
private static final Icon ICO = ImageIconServiceProvider.findInstance().getIconForId(IconID.POPUP_EXPORT_MARKDOWN);
@Nonnull
private static String generateString(final char chr, final int length) {
final StringBuilder buffer = new StringBuilder(length);
for (int i = 0; i < length; i++) {
buffer.append(chr);
}
return buffer.toString();
}
@Nonnull
private static String makeLineFromString(@Nonnull final String text) {
final StringBuilder result = new StringBuilder(text.length());
for (final char c : text.toCharArray()) {
if (Character.isISOControl(c)) {
result.append(' ');
}
else {
result.append(c);
}
}
return result.toString();
}
@Nullable
private static String getTopicUid(@Nonnull final Topic topic) {
return topic.getAttribute(ExtraTopic.TOPIC_UID_ATTR);
}
private static void writeTopic(@Nonnull final Topic topic, @Nonnull final String listPosition, @Nonnull final State state) throws IOException {
final int level = topic.getTopicLevel();
String prefix = "";//NOI18N
final String topicUid = getTopicUid(topic);
if (topicUid != null) {
state.append("<a name=\"").append(topicUid).append("\">").nextLine();//NOI18N
}
if (level < STARTING_INDEX_FOR_NUMERATION) {
final String headerPrefix = generateString('#', topic.getTopicLevel() + 1);//NOI18N
state.append(headerPrefix).append(' ').append(ModelUtils.escapeMarkdownStr(topic.getText())).nextLine();
}
else {
final String headerPrefix = generateString('#', STARTING_INDEX_FOR_NUMERATION + 1);//NOI18N
state.append(prefix).append(headerPrefix).append(' ').append(listPosition).append(' ').append(ModelUtils.escapeMarkdownStr(topic.getText())).nextLine();
}
final ExtraFile file = (ExtraFile) topic.getExtras().get(Extra.ExtraType.FILE);
final ExtraLink link = (ExtraLink) topic.getExtras().get(Extra.ExtraType.LINK);
final ExtraNote note = (ExtraNote) topic.getExtras().get(Extra.ExtraType.NOTE);
final ExtraTopic transition = (ExtraTopic) topic.getExtras().get(Extra.ExtraType.TOPIC);
boolean extrasPrinted = false;
if (transition != null) {
final Topic linkedTopic = topic.getMap().findTopicForLink(transition);
if (linkedTopic != null) {
state.append(prefix).append("*Related to: ")//NOI18N
.append('[')//NOI18N
.append(ModelUtils.escapeMarkdownStr(makeLineFromString(linkedTopic.getText())))
.append("](")//NOI18N
.append("#")//NOI18N
.append(assertNotNull(getTopicUid(linkedTopic)))
.append(")*")//NOI18N
.nextStringMarker()
.nextLine();
extrasPrinted = true;
if (file != null || link != null || note != null) {
state.nextStringMarker().nextLine();
}
}
}
if (file != null) {
final MMapURI fileURI = file.getValue();
state.append(prefix)
.append("> File: ")//NOI18N
.append(ModelUtils.escapeMarkdownStr(fileURI.isAbsolute() ? fileURI.asFile(null).getAbsolutePath() : fileURI.toString())).nextStringMarker().nextLine();
extrasPrinted = true;
}
if (link != null) {
final String url = link.getValue().toString();
final String ascurl = link.getValue().asString(true, true);
state.append(prefix)
.append("> Url: ")//NOI18N
.append('[')//NOI18N
.append(ModelUtils.escapeMarkdownStr(url))
.append("](")//NOI18N
.append(ascurl)
.append(')')//NOI18N
.nextStringMarker()
.nextLine();
extrasPrinted = true;
}
if (note != null) {
if (extrasPrinted) {
state.nextLine();
}
state.append(prefix)
.append("<pre>")//NOI18N
.append(StringEscapeUtils.escapeHtml(note.getValue()))
.append("</pre>")//NOI18N
.nextLine();
}
final Map<String,String> codeSnippets = topic.getCodeSnippets();
if (!codeSnippets.isEmpty()) {
for(final Map.Entry<String,String> e : codeSnippets.entrySet()) {
final String lang = e.getKey();
state.append("```").append(lang).nextLine();
final String body = e.getValue();
for(final String s : StringUtils.split(body,'\n')){
state.append(Utils.removeAllISOControlsButTabs(s)).nextLine();
}
state.append("```").nextLine();
}
}
}
private void writeInterTopicLine(@Nonnull final State state) {
state.nextLine();
}
private void writeOtherTopicRecursively(@Nonnull final Topic t, @Nonnull final String topicListNumStr, final int topicIndex, @Nonnull final State state) throws IOException {
writeInterTopicLine(state);
final String prefix;
if (t.getTopicLevel() >= STARTING_INDEX_FOR_NUMERATION) {
prefix = topicListNumStr + Integer.toString(topicIndex + 1) + '.';//NOI18N
}
else {
prefix = "";//NOI18N
}
writeTopic(t, prefix, state);
int index = 0;
for (final Topic ch : t.getChildren()) {
writeOtherTopicRecursively(ch, prefix, index++, state);
}
}
@Override
public void doExport(@Nonnull final MindMapPanel panel, @Nonnull final JComponent options, @Nullable final OutputStream out) throws IOException {
final State state = new State();
state.append("<!--")//NOI18N
.nextLine()//NOI18N
.append("Generated by NB Mind Map Plugin (https://github.com/raydac/netbeans-mmd-plugin)")//NOI18N
.nextLine();//NOI18N
state.append(new Timestamp(new java.util.Date().getTime()).toString()).nextLine().append("-->").nextLine();//NOI18N
final Topic root = panel.getModel().getRoot();
if (root != null) {
writeTopic(root, "", state);//NOI18N
final Topic[] children = Utils.getLeftToRightOrderedChildrens(root);
for (final Topic t : children) {
writeInterTopicLine(state);
writeTopic(t, "", state);//NOI18N
int indexChild = 0;
for (final Topic tt : t.getChildren()) {
writeOtherTopicRecursively(tt, "", indexChild++, state);//NOI18N
}
}
}
final String text = state.toString();
File fileToSaveMap = null;
OutputStream theOut = out;
if (theOut == null) {
fileToSaveMap = MindMapUtils.selectFileToSaveForFileFilter(panel, Texts.getString("MDExporter.saveDialogTitle"), ".MD", Texts.getString("MDExporter.filterDescription"), Texts.getString("MDExporter.approveButtonText"));
fileToSaveMap = MindMapUtils.checkFileAndExtension(panel, fileToSaveMap, ".MD");//NOI18N
theOut = fileToSaveMap == null ? null : new BufferedOutputStream(new FileOutputStream(fileToSaveMap, false));
}
if (theOut != null) {
try {
IOUtils.write(text, theOut, "UTF-8");
}
finally {
if (fileToSaveMap != null) {
IOUtils.closeQuietly(theOut);
}
}
}
}
@Override
@Nullable
public String getMnemonic() {
return "markdown";
}
@Override
@Nonnull
public String getName(@Nonnull final MindMapPanel panel, @Nullable Topic actionTopic, @Nonnull @MustNotContainNull Topic[] selectedTopics) {
return Texts.getString("MDExporter.exporterName");
}
@Override
@Nonnull
public String getReference(@Nonnull final MindMapPanel panel, @Nullable Topic actionTopic, @Nonnull @MustNotContainNull Topic[] selectedTopics) {
return Texts.getString("MDExporter.exporterReference");
}
@Override
@Nonnull
public Icon getIcon(@Nonnull final MindMapPanel panel, @Nullable Topic actionTopic, @Nonnull @MustNotContainNull Topic[] selectedTopics) {
return ICO;
}
@Override
public int getOrder() {
return 3;
}
}