/*
* Zettelkasten - nach Luhmann
* Copyright (C) 2001-2015 by Daniel Lüdecke (http://www.danielluedecke.de)
*
* Homepage: http://zettelkasten.danielluedecke.de
*
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, see <http://www.gnu.org/licenses/>.
*
*
* Dieses Programm ist freie Software. Sie können es unter den Bedingungen der GNU
* General Public License, wie von der Free Software Foundation veröffentlicht, weitergeben
* und/oder modifizieren, entweder gemäß Version 3 der Lizenz oder (wenn Sie möchten)
* jeder späteren Version.
*
* Die Veröffentlichung dieses Programms erfolgt in der Hoffnung, daß es Ihnen von Nutzen sein
* wird, aber OHNE IRGENDEINE GARANTIE, sogar ohne die implizite Garantie der MARKTREIFE oder
* der VERWENDBARKEIT FÜR EINEN BESTIMMTEN ZWECK. Details finden Sie in der
* GNU General Public License.
*
* Sie sollten ein Exemplar der GNU General Public License zusammen mit diesem Programm
* erhalten haben. Falls nicht, siehe <http://www.gnu.org/licenses/>.
*/
package de.danielluedecke.zettelkasten.tasks.export;
import bibtex.dom.BibtexEntry;
import de.danielluedecke.zettelkasten.CTexExportSettings;
import de.danielluedecke.zettelkasten.ZettelkastenApp;
import de.danielluedecke.zettelkasten.database.BibTex;
import de.danielluedecke.zettelkasten.database.Daten;
import de.danielluedecke.zettelkasten.database.Settings;
import de.danielluedecke.zettelkasten.util.classes.Comparer;
import de.danielluedecke.zettelkasten.util.Constants;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
/**
*
* @author danielludecke
*/
public class ExportTools {
/**
* get the strings for file descriptions from the resource map
*/
private static final org.jdesktop.application.ResourceMap resourceMap
= org.jdesktop.application.Application.getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class).
getContext().getResourceMap(de.danielluedecke.zettelkasten.tasks.export.ExportTask.class);
/**
* Creates a list of certain entry-values (keywords, authors, attachments)
* in plain text format
*
* @param values the list-values (keywords, authors, attachments...) as
* string-array
* @param notfound the title of that list in case no elements have been
* found
* @param header the header of the list (e.g. keywords, authors...)
* @param textagopen
* @param textagclose
* @return a String containing the HTML-formatted list of entry-values
*/
public static String createPlainList(String[] values, String notfound, String header, String textagopen, String textagclose) {
StringBuilder sb = new StringBuilder("");
// if there is no keyword information, tell this the user
if ((null == values) || (values.length < 1)) {
sb.append(notfound).append(System.lineSeparator());
} else {
// create headline indicating that keyword-part starts here
sb.append(textagopen).append(header).append(textagclose).append(System.lineSeparator()).append(System.lineSeparator());
// iterate the keyword array
for (String val : values) {
// and append each author
sb.append(val).append(System.lineSeparator());
}
sb.append(System.lineSeparator());
}
return sb.toString();
}
/**
* This method creates a list of entry-numbers, which are usually referring
* from one entry to other entries (e.g. the follower-numbers or manual
* links to other entries). The list is in plain-text, i.e. it is used for
* exporting entries to the TXT or LaTex-format.
*
* @param values the refrerring entries of which a list should be created
* (i.e. that should appear in this list)
* @param notfound a String which is displayed if the supposed entry has no
* referring entry-numbers (i.e. {@code values} is empty)
* @param header a header which says what kind of list this is
* @param textagopen an open-tag of a tag which surrounds this list.
* typically only used when creating lists for the {@code LaTex} format,
* then e.g. <b>\\subsection{</b> is used.
* @param textagclose a closing-tag of a tag which surrounds this list.
* typically only used when creating lists for the {@code LaTex} format,
* then e.g. <b>}{</b> is used.
* @return the complete list of entries which have been passed through the
* parameter {@code values}, where this list is "formatted" as paragraph for
* usage in plain TXT or LaTex-export.
*/
public static String createPlainCommaList(String[] values, String notfound, String header, String textagopen, String textagclose) {
StringBuilder sb = new StringBuilder("");
// if there is no keyword information, tell this the user
if ((null == values) || (values.length < 1)) {
sb.append(notfound).append(System.lineSeparator());
} else {
// create headline indicating that keyword-part starts here
sb.append(textagopen).append(header).append(textagclose).append(System.lineSeparator()).append(System.lineSeparator());
// iterate the keyword array
for (String val : values) {
// and append each author
sb.append(val).append(", ");
}
sb.setLength(sb.length() - 2);
sb.append(System.lineSeparator());
}
return sb.toString();
}
/**
* This method creates a reference list in the export-format. This method is
* used when exporting entries into html-format. The reference-list is
* created from the used footnotes, i.e. each author-footnote in an entry is
* added to the final reference-list. When exporting to HTML, the
* authors-footnotes are linked with the author-value in the reference-list.
*
* @param dataObj
* @param settingsObj
* @param contentpage the complete html-page that is going to be exported,
* so the author-footnotes can be extracted from this content.
* @param footnotetag the footnote-tag, to identify where a footnote starts
* @param footnoteclose the closing-tag of footnotes, so the author-number
* within the footnote can be extracted
* @param headeropen the header-tag, in case the title "reference list" is
* surrounded by a header-tag
* @param headerclose the header-tag, in case the title "reference list" is
* surrounded by a header-tag
* @param listtype whether the list is formatted in html or plain text. use
* following constants:<br>
* - CConstants.REFERENCE_LIST_TXT<br>
* - CConstants.REFERENCE_LIST_HTML
* @return a converted String containing the reference list with all
* references (authors) that appeared as author-footnote in the export-file
*/
public static String createReferenceList(Daten dataObj, Settings settingsObj, String contentpage, String footnotetag, String footnoteclose, String headeropen, String headerclose, int listtype) {
// now prepare a reference list from possible footnotes
LinkedList<String> footnotes = new LinkedList<>();
// position index for finding the footnotes
int pos = 0;
// get length of footnote-tag, so we know where to look for the author-number within the footnote
int len = footnotetag.length();
// do search as long as pos is not -1 (not-found)
while (pos != -1) {
// find the html-tag for the footnote
pos = contentpage.indexOf(footnotetag, pos);
// if we found something...
if (pos != -1) {
// find the closing quotes
int end = contentpage.indexOf(footnoteclose, pos + len);
// if we found that as well...
if (end != -1) {
// extract footnote-number
String fn = contentpage.substring(pos + len, end);
// and add it to the linked list, if it doesn't already exist
if (-1 == footnotes.indexOf(fn)) {
footnotes.add(fn);
}
// set pos to new position
pos = end;
} else {
pos = pos + len;
}
}
}
StringBuilder sb = new StringBuilder("");
// now we have all footnotes, i.e. the author-index-numbers, in the linked
// list. now we can create a reference list
if (footnotes.size() > 0) {
// first, init the list in html...
sb.append(headeropen);
// append a new headline with the bullet's name
sb.append(resourceMap.getString("referenceListHeading"));
sb.append(headerclose).append(System.lineSeparator());
// iterator for the linked list
Iterator<String> i = footnotes.iterator();
// go through all footnotes
while (i.hasNext()) {
// get author-number-string
String au = i.next();
try {
// convert string to int
int aunr = Integer.parseInt(au);
switch (listtype) {
case Constants.REFERENCE_LIST_TXT:
// prepare html-stuff for authors
sb.append("[").append(au).append("] ").append(dataObj.getAuthor(aunr)).append(System.lineSeparator());
break;
case Constants.REFERENCE_LIST_HTML:
// prepare html-stuff for authors
sb.append("<p class=\"reflist\"><b>[<a name=\"fn_").append(au).append("\">").append(au).append("</a>]</b> ");
sb.append(dataObj.getAuthor(aunr));
sb.append("</p>").append(System.lineSeparator());
break;
}
} catch (NumberFormatException e) {
Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage());
}
}
}
return sb.toString();
}
/**
*
* @param exportDataString
* @param filepath
* @return
*/
public static boolean writeExportData(String exportDataString, File filepath) {
// yet everything is ok...
boolean exportOk = true;
// create filewriter
Writer exportfile = null;
// check whether we have content at all
if (exportDataString != null && !exportDataString.isEmpty()) {
try {
// create output-file in UTF8-encoding
exportfile = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filepath), "UTF8"));
// and try save content in UTF8-encoding to disk...
exportfile.write(exportDataString);
} catch (UnsupportedEncodingException ex) {
try {
// create output-file in default-system-encoding
exportfile = new FileWriter(filepath);
// and save content to disk...
exportfile.write(exportDataString);
} catch (IOException exep) {
// and change indicator
exportOk = false;
Constants.zknlogger.log(Level.SEVERE, exep.getLocalizedMessage());
} finally {
try {
// finally, close filewriter
if (exportfile != null) {
exportfile.close();
}
} catch (IOException ioex) {
// and change indicator
exportOk = false;
Constants.zknlogger.log(Level.SEVERE, ioex.getLocalizedMessage());
}
}
} catch (IOException ex) {
// and change indicator
exportOk = false;
Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage());
Constants.zknlogger.log(Level.SEVERE, "Using following path: {0}", filepath.getAbsolutePath());
} finally {
try {
// finally, close filewriter
if (exportfile != null) {
exportfile.close();
}
} catch (IOException ex) {
// and change indicator
exportOk = false;
Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage());
}
}
} else {
// and change indicator
exportOk = false;
Constants.zknlogger.log(Level.SEVERE, "Error when exporting entries: no content available!");
}
return exportOk;
}
/**
*
* @param dataObj
* @param bibtexObj
* @param exportentries
* @param filepath
* @param resmap
*/
public static void writeBibTexFile(Daten dataObj, BibTex bibtexObj, ArrayList<Object> exportentries, File filepath, org.jdesktop.application.ResourceMap resmap) {
// check whether we have any bibtex-entries at all...
if (bibtexObj.getCount() > 0) {
// this list will contain all found bibkeys within the authors
// of the to be exported entries
ArrayList<String> foundbibkeys = new ArrayList<>();
for (Object exportentrie : exportentries) {
try {
// retrieve zettelnumber
int zettelnummer = Integer.parseInt(exportentrie.toString());
// retrieve the author-index-numbers of each export-entry
int[] entryauthors = dataObj.getAuthorIndexNumbers(zettelnummer);
// check whether entry has any authors...
if (entryauthors != null && entryauthors.length > 0) {
// iterate all author-index-numbers
for (int au : entryauthors) {
// try to retrieve the bibkey from each author
String bibkey = dataObj.getAuthorBibKey(au);
// if we have a valid bibkey that is not already in our list with the
// found bibkeys, add the bibkey to it...
if (bibkey != null && !bibkey.isEmpty() && !foundbibkeys.contains(bibkey)) {
foundbibkeys.add(bibkey);
}
}
}
} catch (NumberFormatException e) {
// do nothing here...
}
}
// check whether we have found any bibkeys at all...
if (foundbibkeys.size() > 0) {
// get export-filepath as string
String orifile = filepath.toString();
// and copy whole filepath except file-extension, to a new string,
// setting the ".bib" extension instead
String exportbibtexfile = orifile.substring(0, orifile.lastIndexOf(".")) + ".bib";
// set export-file-path and check whether file already exists. if yes, ask for
// overwriting
File exportbibfilepath = new File(exportbibtexfile);
// check whether exported bibtex-file aready exists
if (exportbibfilepath.exists()) {
// file exists, ask user to overwrite it...
int optionDocExists = JOptionPane.showConfirmDialog(null, resmap.getString("askForOverwriteFileMsg", "BibTex-", exportbibfilepath.getName()), resmap.getString("askForOverwriteFileTitle"), JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE);
// if the user does *not* choose to overwrite, quit...
if (JOptionPane.NO_OPTION == optionDocExists) {
return;
}
}
// sort bibkeys
Collections.sort(foundbibkeys, new Comparer());
// clear all entries for the export
bibtexObj.clearExportBibtexEntries();
// create iterator for the found bíbkeys
Iterator<String> bibkeys = foundbibkeys.iterator();
while (bibkeys.hasNext()) {
// retrieve bibtex-entry (from the attached bibtex-file) that is associated
// with the bibkey from the exported author
BibtexEntry be = bibtexObj.getEntry(bibkeys.next());
// add entry to the bibtex-export-file
bibtexObj.addBibtexEntryForExport(be);
}
// export all bibtex-entries
bibtexObj.exportBibtexEntries(exportbibfilepath);
} else {
// and log info-message
Constants.zknlogger.log(Level.INFO, resmap.getString("noBibtexEntriesMsg"));
}
} else {
// retrieve filepath of attached file for the message-logging
File caf = bibtexObj.getCurrentlyAttachedFile();
// prepare default log-message. in case we have no attached file, use this string
String cafstring = "<no attached file found>";
if (caf != null) {
// else use the filepath to the bibtex-file that had no vali entries
cafstring = caf.toString();
}
// and log info-message
Constants.zknlogger.log(Level.INFO, resmap.getString("noBibtexEntriesFoundMsg", cafstring));
}
}
/**
* This method converts all footnote-tags ({@code [fn <number>]}) into the
* appropriate LaTex-cite-command, using the bibkeys of the referenced
* author-values (if they have any bibkeys).
* <br><br>
* Each footnote that refers to an author-value with bibkey is converted
* like this:
* <b>{@code \footcite{<bibkey>}}</b>
*
* @param dataObj
* @param content the entry's content, so the footnote-tags can be extracted
* and possible bibkey-values retrieved.
* @param referenceAsFootnote
* @return the content as string value, with all footnote-tags that
* reference to author-values with bibkeys converted to the
* LaTex-cite-command
*/
public static String createLatexFootnotes(Daten dataObj, String content, boolean referenceAsFootnote) {
// if the reference should be in a footenote, create this string now
String footRefOpen = (referenceAsFootnote) ? "\\footcite" : "\\cite";
String footRefClose = "}";
// save find-position
List<Integer> start = new ArrayList<>();
List<Integer> end = new ArrayList<>();
try {
// create foot note patterm
Pattern p = Pattern.compile("\\[fn ([^\\[]*)\\]");
// create matcher
Matcher m = p.matcher(content);
// check for occurences
while (m.find()) {
// save grouping-positions
start.add(m.start());
end.add(m.end());
}
// iterate found positions
for (int i = start.size() - 1; i >= 0; i--) {
// get footnote
String fn = content.substring(start.get(i) + Constants.FORMAT_FOOTNOTE_OPEN.length(), end.get(i) - 1);
// do we have a colon? this indicates a page separator
String[] fnpagenr = fn.split(Pattern.quote(":"));
String pagenr = null;
// more than 1 value means, we have a page numner after colon
if (fnpagenr.length > 1) {
// we assume reference index number at first position
fn = fnpagenr[0];
pagenr = fnpagenr[1];
}
if (null == pagenr || pagenr.isEmpty()) {
pagenr = "{";
} else {
pagenr = "[" + resourceMap.getString("footnotePage") + pagenr + "]{";
}
// check whether footnote is a bibkey, or reference number
int fnnr;
try {
// try to parse token inside footnote tage
// and check whether it is an integer number
// (i.e. a reference to an author)
fnnr = Integer.parseInt(fn);
} catch (NumberFormatException ex) {
// if it is no integer value, check whether
// token is a bibkey
fnnr = dataObj.getAuthorBibKeyPosition(fn);
}
// retrieve author value's bibkey
String bibkey = dataObj.getAuthorBibKey(fnnr);
// check whether we have any bibkey-value
if (bibkey != null && !bibkey.isEmpty()) {
// if we have footnote cite and braces around footnote,
// remove them
if (referenceAsFootnote) {
try {
// footnote starts with (, remove
if (content.charAt(start.get(i) - 1) == '(') {
start.set(i, start.get(i) - 1);
}
// footnote ends with (, remove
if (content.charAt(end.get(i)) == ')') {
end.set(i, end.get(i) + 1);
}
} catch (IndexOutOfBoundsException e) {
}
}
// now that we have the bibkey, replace footnote with cite-tag
content = content.substring(0, start.get(i)) + footRefOpen
+ pagenr + bibkey + footRefClose
+ content.substring(end.get(i));
}
}
} catch (PatternSyntaxException | IndexOutOfBoundsException ex) {
} catch (NumberFormatException ex) {
Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage());
Constants.zknlogger.log(Level.WARNING, "Could not convert author ID into author number!");
}
// return content. this string now has converted footnotes, where the referenced
// author value contains a bibkey.
return content;
}
public static boolean isExportSettingsOk(JFrame frame, Settings settings, int type) {
// when the user wants to export into PDF, open a new dialog where the user
// can make page settings and font-sizes.
if (Constants.EXP_TYPE_TEX == type || Constants.EXP_TYPE_DESKTOP_TEX == type) {
latexExportSettingsDlg = new CTexExportSettings(frame, settings);
// center window
latexExportSettingsDlg.setLocationRelativeTo(frame);
// show window
ZettelkastenApp.getApplication().show(latexExportSettingsDlg);
// retrieve cancel-flag
boolean expcancelled = latexExportSettingsDlg.isCancelled();
// free memory
latexExportSettingsDlg.dispose();
latexExportSettingsDlg = null;
// check whether user cancelled export or not
return !expcancelled;
}
return true;
}
private static CTexExportSettings latexExportSettingsDlg;
}