/* * 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 de.danielluedecke.zettelkasten.database.BibTex; import de.danielluedecke.zettelkasten.database.Daten; import de.danielluedecke.zettelkasten.database.DesktopData; import de.danielluedecke.zettelkasten.database.Settings; import de.danielluedecke.zettelkasten.database.TasksData; import de.danielluedecke.zettelkasten.util.Constants; import de.danielluedecke.zettelkasten.util.HtmlUbbUtil; import de.danielluedecke.zettelkasten.util.Tools; import de.danielluedecke.zettelkasten.util.FileOperationsUtil; import de.danielluedecke.zettelkasten.util.PlatformUtil; import de.danielluedecke.zettelkasten.util.TreeUtil; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Scanner; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import javax.swing.JOptionPane; import javax.swing.tree.DefaultMutableTreeNode; /** * * @author Luedeke */ public class ExportToHtmlTask extends org.jdesktop.application.Task<Object, Void> { /** * Reference to the CDaten object, which contains the XML data of the * Zettelkasten will be passed as parameter in the constructor, see below */ private final Daten dataObj; /** * */ private final DesktopData desktopObj; /** * */ private final BibTex bibtexObj; /** * */ private final TasksData taskinfo; /** * */ private final Settings settingsObj; /** * Indicates whether or not a bibtex-file from the exported entries should * be created or not */ private final boolean exportbibtex; /** * Defines whether each headline of any entry has its entry-number as * prefix. If set to {@code true}, each entry's title will appear as * <i>Entry XY: entry title</i>, if set to {@code false}, a title will just * appear as <i>entry title</i>, or left out if title is empty. */ private final boolean zettelNumberAsPrefix; /** * */ private final boolean isHeadingVisible; /** * */ private final boolean highlightKeywords; /** * This variable stores the parts which should be exported. It's a mix of * ORed constants, see below */ private final int exportparts; /** * indicates which type of data format should be exported to. refer to the * Zettelkasten.view properties file (resources) to see which number is * which file type. */ private final int exporttype; /** * file path to export file */ private final File filepath; /** * */ private StringBuilder exportPage = new StringBuilder(""); /** * */ private StringBuilder exportTableOfContent = new StringBuilder(""); /** * */ private ArrayList<Object> exportentries; /** * */ private boolean exportOk; /** * */ private final boolean createTOC; /** * */ private boolean showOkMessage = true; /** * */ private DefaultMutableTreeNode treenode = null; /** * */ private final javax.swing.JDialog parentDialog; private final javax.swing.JLabel msgLabel; /** * get the strings for file descriptions from the resource map */ private final org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(de.danielluedecke.zettelkasten.ZettelkastenApp.class). getContext().getResourceMap(ExportTask.class); public ExportToHtmlTask(org.jdesktop.application.Application app, javax.swing.JDialog parent, javax.swing.JLabel label, TasksData td, Daten d, DesktopData dt, Settings s, BibTex bto, File fp, ArrayList<Object> ee, int type, int part, DefaultMutableTreeNode n, boolean bibtex, boolean ihv, boolean hkws, boolean numberprefix, boolean toc) { super(app); dataObj = d; settingsObj = s; desktopObj = dt; bibtexObj = bto; filepath = fp; createTOC = toc; exporttype = type; exportparts = part; exportbibtex = bibtex; exportentries = ee; zettelNumberAsPrefix = numberprefix; isHeadingVisible = ihv; exportOk = true; highlightKeywords = hkws; treenode = n; taskinfo = td; parentDialog = parent; msgLabel = label; // the variable "exportentries" stores all entry-numbers of those entries that should be exported. // if this array is null, we assume that *all* entries have to be exported. thus, insert // all entry-numbers here if (null == exportentries) { exportentries = new ArrayList<>(); // copy all entry-numbers to array. remember that the entrynumbers range from 1 to site of file. for (int cnt = 0; cnt < dataObj.getCount(Daten.ZKNCOUNT); cnt++) { // only add entries that are not empty if (!dataObj.isEmpty(cnt + 1)) { exportentries.add(cnt + 1); } } } // show status text msgLabel.setText(resourceMap.getString("msg1")); } @Override protected Object doInBackground() { // Your Task's code here. This method runs // on a background thread, so don't reference // the Swing GUI from here. // prevent task from processing when the file path is incorrect // if no file exists, exit task if (null == filepath) { showOkMessage = false; return null; } // check whether file already exists if (filepath.exists()) { // file exists, ask user to overwrite it... int optionDocExists = JOptionPane.showConfirmDialog(null, resourceMap.getString("askForOverwriteFileMsg", "", filepath.getName()), resourceMap.getString("askForOverwriteFileTitle"), JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE); // if the user does *not* choose to overwrite, quit... if (optionDocExists != JOptionPane.YES_OPTION) { // don't show "export was OK" message in main frame showOkMessage = false; return null; } } int contentsize; int counter; int commentExport; switch (exporttype) { // // here starts the export of the data in XML format // case Constants.EXP_TYPE_HTML: case Constants.EXP_TYPE_ODT: case Constants.EXP_TYPE_DOCX: case Constants.EXP_TYPE_RTF: case Constants.EXP_TYPE_EPUB: // create a new stringbuilder that will contain the final string, i.e. // the html-page which we set to the jeditorpane exportPage = new StringBuilder("<html><head>"); // first of all, append the html-header with all style information exportPage.append(HtmlUbbUtil.getHtmlHeaderForExport(settingsObj)).append("</head><body>"); // get the size of the export data, used for progressbar contentsize = exportentries.size(); // go through all elements of the data file for (counter = 0; counter < exportentries.size(); counter++) { // create export entries exportPage.append(exportEntries(counter)); // update progress bar setProgress(counter, 0, contentsize); } // show status text msgLabel.setText(resourceMap.getString("msg4")); // we don't want ho highlight any terms HtmlUbbUtil.setHighlighTerms(null, HtmlUbbUtil.HIGHLIGHT_STYLE_SEARCHRESULTS, false); // convert all ubb into html... String htmlpage = HtmlUbbUtil.convertUbbToHtml(settingsObj, dataObj, bibtexObj, exportPage.toString(), Constants.FRAME_MAIN, true, true); // show status text msgLabel.setText(resourceMap.getString("msg3")); // create reference-list by extracting the authors from the author-footnotes. exportPage = new StringBuilder(htmlpage + ExportTools.createReferenceList(dataObj, settingsObj, htmlpage, Constants.footnoteHtmlTag, "\"", "<h1>", "</h1>", Constants.REFERENCE_LIST_HTML)); // close html-page exportPage.append("</div></body></html>"); // get the html-content from the stringbuilder that holds //the complete desktopdata in html String finalcontent = exportPage.toString(); // convert special chars (German umlauts) to html-code finalcontent = finalcontent/*.replace("&", "&")*/ .replace("ä", "ä") .replace("Ä", "Ä") .replace("ö", "ö") .replace("Ö", "Ö") .replace("ü", "ü") .replace("Ü", "Ü") .replace("ß", "ß"); // save content that should be exported and written to a file // into a string. the write-procedure is done later, see below exportPage = new StringBuilder(finalcontent); break; case Constants.EXP_TYPE_DESKTOP_HTML: case Constants.EXP_TYPE_DESKTOP_ODT: case Constants.EXP_TYPE_DESKTOP_DOCX: case Constants.EXP_TYPE_DESKTOP_RTF: case Constants.EXP_TYPE_DESKTOP_EPUB: // init variable exportPage = new StringBuilder(""); exportTableOfContent = new StringBuilder("<h1>" + resourceMap.getString("TOC") + "</h1><br>"); // get comment-export-options commentExport = settingsObj.getDesktopCommentExport(); switch (commentExport) { case Constants.EXP_COMMENTS_NO: exportEntriesWithComments(treenode, false); break; case Constants.EXP_COMMENTS_YES: exportEntriesWithComments(treenode, true); break; case Constants.EXP_COMMENTS_ONLY: exportEntriesWithCommentsOnly(treenode); break; } // check whether we have any content if (exportPage.length() > 0) { // if yes, create footnotes. exportPage = new StringBuilder(createFootnotes(createTOC)); } // create dummy-string-builder StringBuilder buf = new StringBuilder(""); // copy html-page to string String preparestring = exportPage.toString(); // iterate each char of the string and encode UTF8-chars for (int i = 0; i < preparestring.length(); i++) { // retrieve char char c = preparestring.charAt(i); // if it's a normal char, append it... if ((int) c < 160) { buf.append(c); } else { // else append entity of unicode-char buf.append("&#").append((int) c).append(";"); } } // return results exportPage = new StringBuilder(buf.toString()); // check for valid content if (exportPage.length() > 0) { // convert special chars (German umlauts) to html-code String desktopContentAsHTML = exportPage.toString(); desktopContentAsHTML = desktopContentAsHTML.replace("ä", "ä") .replace("Ä", "Ä") .replace("ö", "ö") .replace("Ö", "Ö") .replace("ü", "ü") .replace("Ü", "Ü") .replace("ß", "ß"); // save content that should be exported and written to a file // into a string. the write-procedure is done later, see below exportPage = new StringBuilder(desktopContentAsHTML); } else { // and change indicator exportOk = false; Constants.zknlogger.log(Level.SEVERE, "Error when exporting the desktop-content: no content available!"); } break; } // show status text that file will be written msgLabel.setText(resourceMap.getString("msg2")); // do pandoc-conversion here if (exporttype != Constants.EXP_TYPE_HTML && exporttype != Constants.EXP_TYPE_DESKTOP_HTML) { // ------------------------------------------------------------------- // Create string with output content // ------------------------------------------------------------------- // remove "file://" from image tags, otherwise pandoc does not convert // images correctly String outputPage = exportPage.toString(); String replaceStr = "file://"; // on windows os, we have an additional separator char in the file:// if (PlatformUtil.isWindows()) { replaceStr = replaceStr + File.separatorChar; } // replace all file:// orccurences within image tags outputPage = outputPage.replace("<img src=\"" + replaceStr, "<img src=\""); outputPage = outputPage.replaceAll("\\<img ([^\\[]*)\\>", "<p><img $1></p>"); // retrieve output format String outformat = "docx"; switch (exporttype) { case Constants.EXP_TYPE_DESKTOP_DOCX: case Constants.EXP_TYPE_DOCX: outformat = "docx"; break; case Constants.EXP_TYPE_DESKTOP_ODT: case Constants.EXP_TYPE_ODT: outformat = "odt"; break; case Constants.EXP_TYPE_DESKTOP_RTF: case Constants.EXP_TYPE_RTF: outformat = "rtf"; break; case Constants.EXP_TYPE_DESKTOP_EPUB: case Constants.EXP_TYPE_EPUB: outformat = "epub"; break; } // create several temporary pathes, in case the user dows not have // access to a specific tmp-dir String[] temppathes = new String[]{ FileOperationsUtil.getZettelkastenHomeDir(), FileOperationsUtil.getZettelkastenDataDir(settingsObj), FileOperationsUtil.getWorkingDir() }; // file name for tmp-file String temppath = ""; File fn = null; // retrieve zettelkasten home dir as base path for for (String tpath : temppathes) { // create temporary file fn = new File(tpath + Tools.getTimeStampWithMilliseconds() + FileOperationsUtil.getFileName(filepath) + ".html"); // log filepath Constants.zknlogger.log(Level.INFO, "Verwendeter temporärer Pfad für Pandoc-Export: {0}", fn); // write export file exportOk = ExportTools.writeExportData(outputPage, fn); // wait a little bit try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException ex) { Constants.zknlogger.log(Level.SEVERE, "Waiting for export file interrupted..."); } // check if we had success if (exportOk && fn.exists()) { // set temp-path temppath = tpath; // break out of loop break; } } // continue if export is ok if (exportOk && !temppath.isEmpty() && fn != null) { // create output filename File outfn = new File(temppath + FileOperationsUtil.getFileName(filepath) + "." + outformat); // create argument list List<String> args = Arrays.asList(settingsObj.getPandocPath(), "-f", "html", "-t", outformat, "-o", outfn.getAbsolutePath(), fn.getAbsolutePath()); // start pandoc for conversion ProcessBuilder pb = new ProcessBuilder(args); // log parameter, for debugging Constants.zknlogger.log(Level.INFO, Arrays.toString(args.toArray(new String[args.size()]))); // ste process working dir pb = pb.directory(fn.getParentFile()); pb = pb.redirectInput(ProcessBuilder.Redirect.PIPE).redirectError(ProcessBuilder.Redirect.PIPE); // log filepath Constants.zknlogger.log(Level.INFO, "Verwendete temporäre Exportdatei für Pandoc-Export: {0}", outfn); try { // start process Process p = pb.start(); Scanner sc = null; // catch error stream StringBuilder errstr = new StringBuilder(""); // create scanner to receive compiler messages try { sc = new Scanner(p.getInputStream()).useDelimiter(System.lineSeparator()); // write output to text area while (sc.hasNextLine()) { errstr.append(System.lineSeparator()).append(sc.nextLine()); } } catch (IllegalStateException ex) { // log error stream Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } finally { if (sc != null) { sc.close(); } } try { // write output to text area // create scanner to receive compiler messages sc = new Scanner(p.getErrorStream()).useDelimiter(System.lineSeparator()); // write output to text area while (sc.hasNextLine()) { errstr.append(System.lineSeparator()).append(sc.nextLine()); } } catch (IllegalStateException ex) { // log error stream Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } finally { if (sc != null) { sc.close(); } } // log error stream Constants.zknlogger.log(Level.INFO, "Pandoc-Process-Log:" + System.lineSeparator() + "{0}", errstr.toString()); // wait for other process to be finished p.waitFor(); p.destroy(); } catch (IOException | InterruptedException ex) { // and change indicator exportOk = false; Constants.zknlogger.log(Level.SEVERE, "Could not convert file! Either Pandoc is missing or export file not found."); } finally { try { // check whether file exists exportOk = outfn.exists(); // log renaming result if (!exportOk) { Constants.zknlogger.log(Level.SEVERE, "Creating export file failed! Could not save tmp-file to {0}.", outfn.toString()); } else { Constants.zknlogger.log(Level.INFO, "Successfully created tmp-file to {0}.", outfn.toString()); } // check whether file could be moved exportOk = outfn.renameTo(filepath); fn.deleteOnExit(); // log renaming result if (!exportOk) { Constants.zknlogger.log(Level.SEVERE, "Creating export file failed! Could not rename to {0}.", filepath.toString()); } else { Constants.zknlogger.log(Level.INFO, "Successfully renamed export file to {0}.", filepath.toString()); } } catch (SecurityException ex) { // and change indicator exportOk = false; Constants.zknlogger.log(Level.SEVERE, "Could not convert file! Access to destination file path denied!"); } } } else { Constants.zknlogger.log(Level.SEVERE, "Could not create export file!"); } } else { // write export file exportOk = ExportTools.writeExportData(exportPage.toString(), filepath); } // if the user requested a bibtex-export, do this now if (exportbibtex) { // show status text msgLabel.setText(resourceMap.getString("msgBibtextExport")); // write bibtex file ExportTools.writeBibTexFile(dataObj, bibtexObj, exportentries, filepath, resourceMap); } return null; // return your result } @Override protected void succeeded(Object result) { // Runs on the EDT. Update the GUI based on // the result computed by doInBackground(). } @Override protected void finished() { super.finished(); taskinfo.setExportOk(exportOk); taskinfo.setShowExportOkMessage(showOkMessage); // Close Window parentDialog.setVisible(false); parentDialog.dispose(); } /** * * @param counter * @return */ private String exportEntries(int counter) { StringBuilder sb = new StringBuilder(""); try { // retrieve zettelnumber int zettelnummer = Integer.parseInt(exportentries.get(counter).toString()); // check whether the user wants to export titles. if ((exportparts & Constants.EXPORT_TITLE) != 0) { // first check whether we have a title or not String zetteltitle = dataObj.getZettelTitle(zettelnummer).replace("<", "<").replace(">", ">"); // only prepare a title when we want to have the entry's number as title-prefix, // or if we have a title. if (!zetteltitle.isEmpty() || zettelNumberAsPrefix) { // each entry has at least the title "entry" plus its number. // we need this to search through the entries when the user clicks an entry on the // jTreeDesktop in the desktop-window sb.append("<h1>"); // surround entry header with ankh sb.append("<a name=\"z_").append(String.valueOf(zettelnummer)).append("\">"); if (zettelNumberAsPrefix) { sb.append(resourceMap.getString("entryText")).append(" ").append(String.valueOf(zettelnummer)); } // if we have a "real" title, append it... if (!zetteltitle.isEmpty()) { if (zettelNumberAsPrefix) { sb.append(": "); } sb.append(zetteltitle); } sb.append("</a>"); sb.append("</h1>").append(System.lineSeparator()); } } // check whether the user wants to export content if ((exportparts & Constants.EXPORT_CONTENT) != 0) { // get the zettelcontent String zettelcontent = dataObj.getZettelContent(zettelnummer, true).replace("<", "<").replace(">", ">"); // init paragraph with class-attribute, so the user may change style aftwerwards sb.append("<p class=\"zettelcontent\">"); // if we have content, add it. if (!zettelcontent.isEmpty()) { /* // check whether keywords should be highlighted if (highlightkeywords) { // set current entry's keywords as highlight terms CHtml.setHighlighTerms(dataObj.getSeparatedKeywords(zettelnummer)); // and highlight keywords within the entry's content zettelcontent = CHtml.highlightSearchTerms(zettelcontent,null); } */ // check whether keywords should be highlighted if (highlightKeywords) { // set current entry's keywords as highlight terms HtmlUbbUtil.setHighlighTerms(dataObj.getSeparatedKeywords(zettelnummer), HtmlUbbUtil.HIGHLIGHT_STYLE_KEYWORDS, settingsObj.getHighlightWholeWord()); // and highlight keywords within the entry's content zettelcontent = HtmlUbbUtil.highlightSearchTermsInUBB(zettelcontent, HtmlUbbUtil.HIGHLIGHT_STYLE_KEYWORDS); } sb.append(zettelcontent); } else { // else add remark that entry is deleted sb.append("<i>").append(resourceMap.getString("deletedEntry")).append("</i>").append(System.lineSeparator()); } sb.append("</p>"); } // if the user wants to export remarks, do this here. if ((exportparts & Constants.EXPORT_REMARKS) != 0) { // get entry's remarks String remarks = dataObj.getRemarks(zettelnummer); // check whether we have any if (!remarks.isEmpty()) { // set headline indicating that we have remarks here sb.append("<h4>").append(resourceMap.getString("remarksHeader")).append("</h4>").append(System.lineSeparator()); // init paragraph with class-attribute, so the user may change style aftwerwards sb.append("<p class=\"para_remarks\">").append(remarks).append("</p>").append(System.lineSeparator()); } } if ((exportparts & Constants.EXPORT_TIMESTAMP) != 0) { String[] timestamp = dataObj.getTimestamp(zettelnummer); // check whether we have a timestamp at all if (timestamp != null && !timestamp[0].isEmpty()) { // prepare the html-part sb.append("<p class=\"timestamp\">"); // and add the created-timestamp sb.append(resourceMap.getString("timestampCreated")).append(" ").append(Tools.getProperDate(timestamp[0], false)); // check whether we have a modified-timestamp // if we have a modified-stamp, add it... if (timestamp.length > 1 && !timestamp[1].isEmpty()) { sb.append("<br>").append(resourceMap.getString("timestampEdited")).append(" ").append(Tools.getProperDate(timestamp[1], false)); } // and close the tags of the html-part sb.append("</p>").append(System.lineSeparator()); } } // check whether the user wants to export authors if ((exportparts & Constants.EXPORT_AUTHOR) != 0 && dataObj.hasAuthors(zettelnummer)) { sb.append(createHTMLList(dataObj.getAuthors(zettelnummer), resourceMap.getString("NoAuthor"), resourceMap.getString("authorHeader"), "author")); } // check whether user wants to export keywords. if ((exportparts & Constants.EXPORT_KEYWORDS) != 0 && dataObj.hasKeywords(zettelnummer)) { sb.append(createHTMLList(dataObj.getKeywords(zettelnummer, true), resourceMap.getString("NoKeyword"), resourceMap.getString("keywordHeader"), "list_keywords")); } if ((exportparts & Constants.EXPORT_LINKS) != 0 && dataObj.hasAttachments(zettelnummer)) { sb.append(createHTMLList(dataObj.getAttachmentsAsString(zettelnummer, true), resourceMap.getString("NoAttachment"), resourceMap.getString("attachmentHeader"), "list_attachments")); } if ((exportparts & Constants.EXPORT_MANLINKS) != 0 && dataObj.hasManLinks(zettelnummer)) { sb.append(createHTMLCommaList(dataObj.getManualLinksAsString(zettelnummer), resourceMap.getString("NoManLinks"), resourceMap.getString("manlinksHeader"), "list_manlinks")); } if ((exportparts & Constants.EXPORT_LUHMANN) != 0 && dataObj.hasLuhmannNumbers(zettelnummer)) { sb.append(createHTMLCommaList(dataObj.getLuhmannNumbersAsString(zettelnummer), resourceMap.getString("NoLuhmann"), resourceMap.getString("luhmannHeader"), "list_luhmann")); } } catch (NumberFormatException e) { // leave out first char, which is always a "H", set by the method // "createExportEntries()". sb.append("<h1 class=\"deskhead\">").append(exportentries.get(counter).toString().substring(2)).append("</h1>").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 formatted in HTML, so it can be used * for HTML-export. * * @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 cssclass a CSS-class-reference that is used as * {@code class}-attribute for the HTML-p element. * @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 HTML-export. */ private String createHTMLCommaList(String[] values, String notfound, String header, String cssclass) { StringBuilder sb = new StringBuilder(""); // if there is no keyword information, tell this the user if ((null == values) || (values.length < 1)) { sb.append("<p><i>").append(notfound).append("</i></p>").append(System.lineSeparator()); } else { // create headline indicating that keyword-part starts here sb.append("<h4>").append(header).append("</h4>").append(System.lineSeparator()); // init paragraph with class-attribute, so the user may change style aftwerwards sb.append("<p class=\"").append(cssclass).append("\">"); // iterate the keyword array for (String val : values) { // and append each author sb.append(val).append(", "); } sb.setLength(sb.length() - 2); sb.append("</p>").append(System.lineSeparator()); } return sb.toString(); } /** * Creates a list of certain entry-values (keywords, authors, attachments) * in HTML-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 cssclass a class-attribute that should be added to the HTML-tags, * so the user can format the layout of the exported html-page afterwards. * @return a String containing the HTML-formatted list of entry-values */ private String createHTMLList(String[] values, String notfound, String header, String cssclass) { StringBuilder sb = new StringBuilder(""); // if there is no keyword information, tell this the user if ((null == values) || (values.length < 1)) { sb.append("<p><i>").append(notfound).append("</i></p>").append(System.lineSeparator()); } else { // create headline indicating that keyword-part starts here sb.append("<h4>").append(header).append("</h4>").append(System.lineSeparator()); // init paragraph with class-attribute, so the user may change style aftwerwards sb.append("<ul class=\"").append(cssclass).append("\">"); // iterate the keyword array for (String val : values) { // autoconvert url's to hyperlinks // val = CHtml.convertHyperlinks(val.replace("<", "<").replace(">", ">")); // and append each author sb.append("<li>").append(val.replace("<", "<").replace(">", ">")).append("</li>").append(System.lineSeparator()); } sb.append("</ul>").append(System.lineSeparator()); } return sb.toString(); } /** * This method creates a HTML-page that contains all the desktop-data (i.e. * all entries or modified entries on the desktop), where the output of the * HTML-page is identical to the view of the desktop. I.e. if the user * turned off entry-titles, these are also not exportet to the output file. * * @param node the starting point for the jTree-enumeration, either the root * node or a bullet (if only a bullet should be exported, see * {@link #exportDesktopBullet() exportDesktopBullet()}). * @param sb the stringbuilder which finally will contain all content in * html-format * @param exportcomments {@code true} if comments should also be exported, * {@code false} if comments should not be exported. */ private void exportEntriesWithComments(DefaultMutableTreeNode node, boolean exportcomments) { // get a list with all children of the node Enumeration en = node.children(); // go through all children while (en.hasMoreElements()) { // get the child node = (DefaultMutableTreeNode) en.nextElement(); // if the child is a bullet... if (node.getAllowsChildren()) { // append bullet-point exportPage.append(createExportBullet(node)); // check whether comments should be exported as well if (exportcomments) { // retrieve comment String com = desktopObj.getComment(TreeUtil.getNodeTimestamp(node), "<br>"); // check for valid comment if (com != null && !com.isEmpty()) { // append comment exportPage.append("<p class=\"comment\">").append(com).append("</p>").append(System.lineSeparator()); } } } // now we know we have an entry. so get the entry number... else { // and append the html-text of the entry... exportPage.append(createExportEntry(node)); // check whether comments should be exported as well if (exportcomments) { // retrieve comment String com = desktopObj.getComment(TreeUtil.getNodeTimestamp(node), "<br>"); // check for valid comment if (com != null && !com.isEmpty()) { // append comment and replace [br]-tags exportPage.append("<p class=\"comment\">").append(com).append("</p>").append(System.lineSeparator()); } } } // when the new node also has children, call this method again, // so we go through the strucuture recursively... if (node.getChildCount() > 0) { exportEntriesWithComments(node, exportcomments); } } } /** * This method prepares the html-content for an exported entry. this method * is used by {@link #exportEntriesWithComments(javax.swing.tree.DefaultMutableTreeNode, java.lang.StringBuilder, boolean) exportEntriesWithComments() * } * and * {@link #exportEntriesWithCommentsOnly(javax.swing.tree.DefaultMutableTreeNode, java.lang.StringBuilder) exportEntriesWithCommentsOnly()}. * * @param node the entry-node, needed for timestamp and entry-text * @return a html-snippet with the entry as content */ private String createExportEntry(DefaultMutableTreeNode node) { // retrieve node's timestamp String timestamp = TreeUtil.getNodeTimestamp(node); // we now want to check whether the user has made modifications to the entry's // content, which are only made to the desktop (the content of the entry in the main database // is not changed, so you can edit the desktop-entry without changing the entry's original // content - this is useful when you want to add some words/phrases between entries etc., which // should be applied only to the final text on the desktop, but not to the original entries). // // in case we have modified an entry on the desktop, this entry has a "content" element. to // retrieve the correct entry, we need to look for the unique timestamp of that entry - since // an entry could appear multiple times on the desktop, thus the entry number itself is no // valid value for retrieving the right entry. Therefore, each treenode has a user-object // assigned, which holds the unique timestamp String text = desktopObj.retrieveModifiedEntryContentFromTimestamp(timestamp); // retrieve entry-number int nr = TreeUtil.extractEntryNumberFromNode(node); // if nothing found, retrieve regular entry // that means, if the entry with the unique-timestamp has no or an empty content-element, the // entry was not modified - thus we retrieve the "original" entry. if (null == text || text.isEmpty()) { text = HtmlUbbUtil.getHtmlContentForDesktop(dataObj, bibtexObj, settingsObj, nr, isHeadingVisible, zettelNumberAsPrefix, true, true); } // else if we have a modified entry-content, we still need to convert its // ubb-tags to HTML. this is done here... else { // get the html-text for an entry which content is passed as parameter... text = HtmlUbbUtil.getHtmlContentForDesktop(dataObj, bibtexObj, settingsObj, text, nr, isHeadingVisible, zettelNumberAsPrefix, true, true); } // if the user wishes to remove multiple line-breaks, do this here if (settingsObj.getRemoveLinesForDesktopExport()) { text = text.replace("<br><br>", "<br>"); } // now create the reference-ankh, so we can use the "scrollToReference" method // of the jEditorPane easily each time the user clicks on an entry in the jTree // to scroll to that entry in the text field. StringBuilder sb = new StringBuilder(""); sb.append("<a name=\""); sb.append("entry").append(timestamp); sb.append("\"> </a>").append(System.lineSeparator()); sb.append(text); // retrieve entry's title String title = dataObj.getZettelTitle(nr); // if we have no title, use enty number instead if (title.isEmpty()) { title = resourceMap.getString("entryText") + " " + String.valueOf(nr); } // retrieve node-level, so we can use margins according the the depth of the node in the outline structure int lvl = node.getLevel(); // set maximum level depth if (lvl > 5) { lvl = 5; } // convert to string for css-class String level = String.valueOf(lvl); // create toc-entry exportTableOfContent.append("<p class=\"tocentry").append(level).append("\"><a href=\"#entry").append(timestamp).append("\">").append(title).append("</a></p>").append(System.lineSeparator()); // return result return sb.toString(); } /** * This method prepares the html-content for an exported bullet-point. this * method is used by {@link #exportEntriesWithComments(javax.swing.tree.DefaultMutableTreeNode, java.lang.StringBuilder, boolean) exportEntriesWithComments() * } * and * {@link #exportEntriesWithCommentsOnly(javax.swing.tree.DefaultMutableTreeNode, java.lang.StringBuilder) exportEntriesWithCommentsOnly()}. * * @param node the bullet-node, needed for timestamp and title-text * @return a html-snippet with the bullet as headline */ private String createExportBullet(DefaultMutableTreeNode node) { StringBuilder sb = new StringBuilder(""); // get node's timestamp String timestamp = TreeUtil.getNodeTimestamp(node); // retrieve bullet-level int bulletlevel = node.getLevel(); // create html-tags for bullet sb.append("<h").append(String.valueOf(bulletlevel)).append(">"); // now create the reference-ankh, so we can use the "scrollToReference" method // of the jEditorPane easily each time the user clicks on an entry in the jTree // to scroll to that entry in the text field. sb.append("<a name=\""); sb.append("entry").append(timestamp); sb.append("\"> </a>"); sb.append(TreeUtil.getNodeText(node)); sb.append("</h").append(String.valueOf(bulletlevel)).append(">").append(System.lineSeparator()); // retrieve node-level, so we can use margins according the the depth of the node in the outline structure int lvl = node.getLevel(); int headerlvl = lvl + 1; // set maximum level depth if (lvl > 5) { lvl = 5; } if (headerlvl > 5) { headerlvl = 5; } // convert to string for css-class String level = String.valueOf(lvl); String headerlevel = String.valueOf(headerlvl); // add bullet-point to table of content exportTableOfContent.append("<h").append(headerlevel).append(" class=\"tocheader").append(level).append("\"><a href=\"#entry").append(timestamp).append("\">").append(TreeUtil.getNodeText(node)).append("</a></h").append(headerlevel).append(">").append(System.lineSeparator()); return sb.toString(); } /** * This method creates a HTML-page that contains all the desktop-data (i.e. * all entries or modified entries on the desktop) which have comments * associated. Only bullet points or entries with comments are being * exported.<br><br> * The output of the HTML-page is identical to the view of the desktop. I.e. * if the user turned off entry-titles, these are also not exportet to the * output file. * * @param node the starting point for the jTree-enumeration, either the root * node or a bullet (if only a bullet should be exported, see * {@link #exportDesktopBullet() exportDesktopBullet()}). * @param sb the stringbuilder which finally will contain all content in * html-format * @param sb the stringbuilder which finally will contain all content in * html-format */ private void exportEntriesWithCommentsOnly(DefaultMutableTreeNode node) { // get a list with all children of the node Enumeration en = node.children(); // go through all children while (en.hasMoreElements()) { // get the child node = (DefaultMutableTreeNode) en.nextElement(); // retrieve comment String com = desktopObj.getComment(TreeUtil.getNodeTimestamp(node), "<br>"); // check for valid comment if (com != null && !com.isEmpty()) { // if the child is a bullet... if (node.getAllowsChildren()) { // append bullet-point exportPage.append(createExportBullet(node)); // append comment exportPage.append("<p class=\"comment\">").append(com).append("</p>").append(System.lineSeparator()); } // now we know we have an entry. so get the entry number... else { // and append the html-text of the entry... exportPage.append(createExportEntry(node)); // append comment exportPage.append("<p class=\"comment\">").append(com).append("</p>").append(System.lineSeparator()); } } // when the new node also has children, call this method again, // so we go through the strucuture recursively... if (node.getChildCount() > 0) { exportEntriesWithCommentsOnly(node); } } } /** * * @param createTOC * @return */ private String createFootnotes(boolean createTOC) { // now prepare a reference list from possible footnotes LinkedList<String> footnotes = new LinkedList<>(); // position index for finding the footnotes int pos = 0; // we need the content of the stringbuilder in a string that we can search through String dummysb = exportPage.toString(); // do search as long as pos is not -1 (not-found) while (pos != -1) { // find the html-tag for the footnote pos = dummysb.indexOf(Constants.footnoteHtmlTag, pos); // if we found something... if (pos != -1) { // find the closing quotes int end = dummysb.indexOf("\"", pos + Constants.footnoteHtmlTag.length()); // if we found that as well... if (end != -1) { // extract footnote-number String fn = dummysb.substring(pos + Constants.footnoteHtmlTag.length(), 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 + Constants.footnoteHtmlTag.length(); } } } // 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) { // insert a paragraph for space exportPage.append("<p> </p>").append(System.lineSeparator()); // first, init the list in html and add title "references" exportPage.append("<h1>").append(resourceMap.getString("referenceListHeading")).append("</h1>").append(System.lineSeparator()); // open unordered list-tag exportPage.append("<ul>").append(System.lineSeparator()); // iterator for the linked list Iterator<String> i = footnotes.iterator(); while (i.hasNext()) { String au = i.next(); try { int aunr = Integer.parseInt(au); exportPage.append("<li class=\"reflist\"><b>[<a name=\"fn_").append(au).append("\">").append(au).append("</a>]</b> "); exportPage.append(dataObj.getAuthor(aunr)); exportPage.append("</li>").append(System.lineSeparator()); } catch (NumberFormatException e) { Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); } } // close unordered list-tag exportPage.append("</ul>").append(System.lineSeparator()); } // add table of contents, if requested if (createTOC) { exportPage.insert(0, exportTableOfContent.toString() + "<br><p> </p><br>"); } // and if so, insert style-definition exportPage.insert(0, HtmlUbbUtil.getHtmlHeaderForDesktopExport(settingsObj)); // and close tags exportPage.append("</body></html>"); // return result return exportPage.toString(); } }