Java tutorial
/* * Zettelkasten - nach Luhmann ** Copyright (C) 2001-2014 by Daniel Ldecke (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 knnen es unter den Bedingungen der GNU * General Public License, wie von der Free Software Foundation verffentlicht, weitergeben * und/oder modifizieren, entweder gem Version 3 der Lizenz oder (wenn Sie mchten) * jeder spteren Version. * * Die Verffentlichung 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 FR 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.database; import de.danielluedecke.zettelkasten.CMakeFormImage; import de.danielluedecke.zettelkasten.ZettelkastenView; import de.danielluedecke.zettelkasten.util.classes.Comparer; import de.danielluedecke.zettelkasten.util.Constants; import de.danielluedecke.zettelkasten.util.HtmlUbbUtil; import de.danielluedecke.zettelkasten.util.Tools; import java.io.File; import java.util.ArrayList; import java.util.Arrays; 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.JOptionPane; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.IllegalAddException; import org.jdom2.IllegalDataException; import org.jdom2.IllegalNameException; /** * This is the data class. This class stores all the programme data in a JDOM * XML Tree. It also provides typical methods like getters and setters, stores * the file path, the modified state and so on. All relevant operations need for * managing the data file can be found here, except loading (open file) and saving * operations. These can be found in "CLoadSave.java". * * @author danielludecke */ public class Daten { // // IMPORTANT! // // REMEMBER TO CHANGE THIS VERSION NUMBER WHEN DATA STRUCTURE HAS CHANGED! // /** * Constant for the current file version */ private static final String currentVersion = "3.6"; public static final String backwardCompatibleVersion = "3.4"; /** * A refrence to the settings class */ private final Settings settings; /** * A refrence to the settings class */ private final BibTex bibtexObj; /** * A refrence to the synonyms class */ private final Synonyms synonymsObj; /** * XML Document that Stores the main data */ private Document zknFile; /** * XML Document that Stores the data of entries * that should be exportet to .zkn3-format */ private Document zknFileExport; /** * XML Document that Stores the author data */ private Document authorFile; /** * XML Document that Stores the keyword data */ private Document keywordFile; /** * XML Document that Stores the meta information of the zettelkasten-data */ private Document metainfFile; /** * Stores the index number of the currently displayed entry */ private int zettelPos; /** * state variable that tracks changes to the data file */ private boolean modified; /** * state variable that tracks changes to the meta-information-file */ private boolean metamodified; /** * Indicates whether saving the data file was ok, or whether an error occured. */ private boolean saveOk; /** * This array stores all watched entries in the order the user "surfed" * through the entries, so we have a history-function. The user can then go * back to previously accessed entries and so on... */ private int[] history; /** * Indicates the current position in that array, i.e. when the user activates * the history function, we have to know which element of the history array is * currently "active". */ private int historyPosition; /** * The array's maximum limit does not automatically equal the amount of saved * history steps. so we use this as internal counter. */ private int historyCount; /** * Stores the files which we want to retrieve from the main data file (filename.zkn3). * This file is a zip-container with the file-extension ".zkn3" and contains several XML-Files. * We cannot retrieve those file simply with the method "zip.getNextEntry()", since the SAXBuilder * closes the zip-inputstream. To retrieve all XML-files from within the zip-file, without saving * them temporarily to harddisk(!), we need to re-open the zip-container again for each file. * * See class "CLoadDialog.java" for more details. */ private final List<String> filesToLoad = new ArrayList<String>(); /** * This list stores all follower and follower's follower of an entry */ private final List<Integer> allLuhmannNumbers = new ArrayList<Integer>(); /** * here we store whether the keyword list (keywordFile) * is up to date or not. if it's up to date, we do not need * to start the CShowKeywordList task, when the user switches * to that tab in the main window's tab pane */ private boolean keywordlistUpToDate; /** * here we store whether the author list (authorFile) * is up to date or not. if it's up to date, we do not need * to start the CShowAuthorList task, when the user switches * to that tab in the main window's tab pane */ private boolean authorlistUpToDate; /** * here we store whether the title list (titles of entries) * is up to date or not. if it's up to date, we do not need * to start the CShowTitleList task, when the user switches * to that tab in the main window's tab pane */ private boolean titlelistUpToDate; /** * here we store whether the cluster list (related keywords) * is up to date or not. if it's up to date, we do not need * to rebuild that list/tree, when the user switches * to that tab in the main window's tab pane */ private boolean clusterlistUpToDate; /** * here we store whether the attachment-list (entries' attachments) * is up to date or not. if it's up to date, we do not need * to rebuild that list/tree, when the user switches * to that tab in the main window's tab pane */ private boolean attachmentlistUpToDate; /** * This variables stored the ID of the last added entry. */ private String lastAddedZettelID = null; // // constant variables // /** * Constant used as parameter for the getCount method * Retrieves the count of elements of the main data file (zknfile) */ public static final int ZKNCOUNT = 1; /** * Constant used as parameter for the getCount method * Retrieves the count of elements of the keyword file (keywordfile) */ public static final int KWCOUNT = 2; /** * Constant used as parameter for the getCount method * Retrieves the count of elements of the author file (authorfile) */ public static final int AUCOUNT = 3; /** * One of the return values when adding an entry * (see {@link #addEntry(java.lang.String, java.lang.String, java.lang.String[], java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, int) addEntry()}). * <br><br> * This value indicates that everything was OK when adding the entry. */ public static final int ADD_ENTRY_OK = 1; /** * One of the return values when adding an entry as follower (trailing-entry) * (see {@link #addEntry(java.lang.String, java.lang.String, java.lang.String[], java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, int) addEntry()}). * <br><br> * This value indicates that everything was OK when adding the follower-entry. */ public static final int ADD_LUHMANNENTRY_OK = 2; /** * One of the return values when adding an entry * (see {@link #addEntry(java.lang.String, java.lang.String, java.lang.String[], java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, int) addEntry()}). * <br><br> * This value indicates that a general error occured when adding the entry. */ public static final int ADD_ENTRY_ERR = 3; /** * One of the return values when adding an entry as follower (trailing-entry) * (see {@link #addEntry(java.lang.String, java.lang.String, java.lang.String[], java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, int) addEntry()}). * <br><br> * This value indicates that adding the entry in general was OK, but that this entry <i>could not</i> be * added as follower-entry, e.g. in case the parent-entry already had such an index-number as follower... */ public static final int ADD_LUHMANNENTRY_ERR = 4; /** * Indicates the maximum amount of saved history steps */ final int HISTORY_MAX = 50; /** * Reference to the main frame. */ private final ZettelkastenView zknframe; public static final String DOCUMENT_ZETTELKASTEN = "zettelkasten"; public static final String DOCUMENT_AUTHORS = "authors"; public static final String DOCUMENT_KEYWORDS = "keywords"; public static final String ELEMENT_ENTRY = "entry"; public static final String ELEMENT_ZETTEL = "zettel"; public static final String ELEMENT_TITLE = "title"; public static final String ELEMENT_CONTENT = "content"; public static final String ELEMENT_KEYWORD = "keywords"; public static final String ELEMENT_AUTHOR = "author"; public static final String ELEMENT_AUTHORS = "authors"; public static final String ELEMENT_REMARKS = "misc"; public static final String ELEMENT_MANLINKS = "manlinks"; public static final String ELEMENT_ATTACHMENTS = "links"; public static final String ELEMENT_ATTCHILD = "link"; public static final String ELEMENT_TRAILS = "luhmann"; public static final String ELEMENT_LUHMANN_NUMBER = "luhindex"; public static final String ELEMEMT_DESCRIPTION = "description"; public static final String ELEMENT_ATTACHMENT_PATH = "attachmentpath"; public static final String ELEMENT_IMAGE_PATH = "imagepath"; public static final String ELEMENT_VERSION_INFO = "version"; // attributes of the "zettel" element public static final String ATTRIBUTE_RATING = "rating"; public static final String ATTRIBUTE_RATINGCOUNT = "ratingcount"; public static final String ATTRIBUTE_TIMESTAMP_CREATED = "ts_created"; public static final String ATTRIBUTE_TIMESTAMP_EDITED = "ts_edited"; public static final String ATTRIBUTE_ZETTEL_ID = "zknid"; public static final String ATTRIBUTE_NEXT_ZETTEL = "nextzettel"; public static final String ATTRIBUTE_PREV_ZETTEL = "prevzettel"; public static final String ATTRIBUTE_FIRST_ZETTEL = "firstzettel"; public static final String ATTRIBUTE_LAST_ZETTEL = "lastzettel"; // attributes of keyword and author elements public static final String ATTRIBUTE_AUTHOR_ID = "authid"; public static final String ATTRIBUTE_KEYWORD_ID = "keywid"; public static final String ATTRIBUTE_AUTHOR_TIMESTAMP = "authts"; public static final String ATTRIBUTE_AUTHOR_BIBKEY = "bibkey"; public static final String ATTRIBUTE_KEYWORD_TIMESTAMP = "keywts"; public static final String ATTRIBUTE_FREQUENCIES = "f"; /** * The file format of the zknFile<br> * <br> * root element: <b>zettelkasten</b><br> * <br> * <u>attributes</u> of the <b>zettelkasten</b> element:<br> * <i>firstzettel</i> - a reference to the first entry (number) in the display order<br> * <i>lastzettel</i> - a reference to the last entry (number) in the display order<br> * <br> * <u>children</u> of the <b>zettelkasten</b> element:<br> * <ul> * <li><b>zettel</b><br> * An ID indicating the fixed position of a "zettel" (entry) is not needed, * since new entries which are e.g. inserted "in between", will be added to the * end of the xml file. However, we have a unique ID for each entry for * synchronization of different data bases etc. * <br> * <br> * <u>attributes</u>: * <ul> * <li><i>zknid</i> - a unique ID for this entry</li> * <li><i>nextzettel</i> - reference to the next entry (number) in the display order</li> * <li><i>prevzettel</i> - reference to the previous entry (number) in the display order</li> * <li><i>rating</i> - (optional) the personal rating for this entry</li> * <li><i>ratingcount</i> - (optional) counts how often this entry has been rated</li> * <li><i>ts_created</i> - the timestamp when this entry was first created</li> * <li><i>ed_edited</i> - the timestamp when this entry was last modified</li> * </ul> * </li> * </ul> * * <br> * <u>children</u> of <b>zettel</b><br> * <ul> * <li><i>title</i> - the title of the entry</li> * <li><i>content</i> - the content of each slip, the main text. may contain html-syntax due to formatting.</li> * <li><i>author</i> - one or more index number(s) indicating the entry of the XML-data-file "authorFile", separated by commas</li> * <li><i>keywords</i> - one or more index number(s) indicating the entry of the XML-data-file "keywordFile", separated by commas</li> * <li><i>manlinks</i> - manual links from the user, referring to other entries, i.e. their index numbers</li> * <li><i>links</i> - list of links (attachments) to files or websites, separated by new sub-elements called "link" * <ul> * <li><i>link</i> - the single entriy (attachment) of the links</li> * <li><i>link</i> - ...</li> * </ul> * </li> * <li><i>misc</i> - miscellaneous or orther comments, text, etc.</li> * <li><i>luhmann</i> - used to indicate follower-entries (trails). follower-entries are dispayed as "sub-entries" of an entry, thus enabling a structured overview of entries.</li> * </ul> * <br> * <br> * Now, the strings/content related to the index numbers in "author" and "keywords" are * stored in separetd XML files: authorFile and keywordFile (see variable declaration above).<br> * <br> * Sample of a possible authorFile:<br> * <br> * <authors><br> * <entry f="9">Luhmann, Niklas (1984): Soziale Systeme. Frankfurt/Main: Suhrkamp</entry><br> * <entry f="3" bibkey="bae2005">Baecker, Dirk (2005): Form und Formen der Kommunikation. Frankfurt/Main: Suhrkamp</entry><br> * </authors><br> * <br> * <br> * Sample of a possible keywordFile:<br> * <br> * <keywords><br> * <entry>Soziale Systeme</entry><br> * <entry>Habermas</entry><br> * <entry>Grundlagen Systemtheorie</entry><br> * <entry>Phnomenologie</entry><br> * <entry>Wissenschaft</entry><br> * <entry>System und Umwelt</entry><br> * </keywords><br> * @param zkn * @param s * @param syn * @param bib */ public Daten(ZettelkastenView zkn, Settings s, Synonyms syn, BibTex bib) { // initiate the JDOM files and all other data, thus // creating an empty "Zettelkasten" zknframe = zkn; settings = s; synonymsObj = syn; bibtexObj = bib; zettelPos = 1; initZettelkasten(); } // TODO prfen, ob berall, wo notwendig, author-/keyword ID und timestamp attribute aktualisiert werden /** * Initiates the global variables and creates empty JDom objects * <br><br> * <b>Warning!</b> This method does <i>not</i> clear the filePath-variable, so * be sure you have set the filePath to null manually, if necessary. */ public final void initZettelkasten() { // reset all global variables modified = false; zknframe.resetBackupNecessary(); zknFile = null; authorFile = null; keywordFile = null; metainfFile = null; zknFileExport = null; // init the history array history = new int[HISTORY_MAX]; // current position in the history array refers to the first element historyPosition = 0; // indicates that we have one (initial) element historyCount = 1; // the one and only element is the first entry history[0] = 1; // no update to the tabbed panes in the main window when nothing is loaded keywordlistUpToDate = true; authorlistUpToDate = true; titlelistUpToDate = true; clusterlistUpToDate = true; attachmentlistUpToDate = true; // create "empty" XML JDom objects zknFile = new Document(new Element(DOCUMENT_ZETTELKASTEN)); authorFile = new Document(new Element(DOCUMENT_AUTHORS)); keywordFile = new Document(new Element(DOCUMENT_KEYWORDS)); // prepare the metainformation-file metainfFile = new Document(new Element("metainformation")); // first create an attribute for the fileversion-number Element fileversion = new Element(ELEMENT_VERSION_INFO); fileversion.setAttribute("id", currentVersion); // and add it to the document metainfFile.getRootElement().addContent(fileversion); // than create an empty description and add it Element desc = new Element(ELEMEMT_DESCRIPTION); metainfFile.getRootElement().addContent(desc); // than create an empty atachment-path and add it Element attpath = new Element(ELEMENT_ATTACHMENT_PATH); metainfFile.getRootElement().addContent(attpath); // than create an empty atachment-path and add it Element imgpath = new Element(ELEMENT_IMAGE_PATH); metainfFile.getRootElement().addContent(imgpath); // init zettel-position-index zettelPos = 1; // reset references to first and last entry setFirstZettel(-1); setLastZettel(-1); // here we add all files which are stored in the zipped data-file in a list-array filesToLoad.clear(); filesToLoad.add(Constants.metainfFileName); filesToLoad.add(Constants.zknFileName); filesToLoad.add(Constants.authorFileName); filesToLoad.add(Constants.keywordFileName); filesToLoad.add(Constants.bookmarksFileName); filesToLoad.add(Constants.searchrequestsFileName); filesToLoad.add(Constants.desktopFileName); filesToLoad.add(Constants.desktopModifiedEntriesFileName); filesToLoad.add(Constants.desktopNotesFileName); filesToLoad.add(Constants.synonymsFileName); filesToLoad.add(Constants.bibTexFileName); // reset list allLuhmannNumbers.clear(); } /** * This method returns the version of the fileformat. the filestructure and data-storing * might change due to further development of this programm, so here we can check * for the current fileformat-version if necessary. This information is stored in the * metainformation-file. * <br><br> * This may differ from the version of the <i>current</i> fileformat. see * {@link #getCurrentVersionInfo() getCurrentVersionInfo()} to retrieve the version-number * of the current fileformat. * * @return a string containing the version-number of the zettelkasten-file-format, or {@code null} if * no such attribute exists */ public String getVersionInfo() { // retrieve version-element Element el = metainfFile.getRootElement().getChild(ELEMENT_VERSION_INFO); // check whether it's null or not if (null == el) { // log error Constants.zknlogger.log(Level.WARNING, "Could not read file version info. XML-element is null!"); return null; } // get id-attribute String id = el.getAttributeValue("id"); // check for valid value if (null == id || id.isEmpty()) { // log error Constants.zknlogger.log(Level.WARNING, "Could not read file version info. XML-attribute is null!"); return null; } // return the attribute value return id; } /** * Retrieves the user defined path to attachments * @return the user defined path to attachments */ public File getUserAttachmentPath() { // retrieve version-element Element el = metainfFile.getRootElement().getChild(ELEMENT_ATTACHMENT_PATH); // check whether it's null or not if (null == el || el.getText().trim().isEmpty()) { return null; } // else return the attribute value return new File(el.getText()); } /** * Ssaves the user defined path to attachments * @param path the user defined path to attachments */ public void setUserAttachmentPath(String path) { // retrieve version-element Element el = metainfFile.getRootElement().getChild(ELEMENT_ATTACHMENT_PATH); // check whether it's null or not if (null == el) { // than create an empty atachment-path and add it el = new Element(ELEMENT_ATTACHMENT_PATH); metainfFile.getRootElement().addContent(el); } // check for valid parameter if (path != null) { // set new path el.setText(path); // and change modified state setMetaModified(true); } } /** * Retrieves the user defined path to images * @return the user defined path to images */ public File getUserImagePath() { // retrieve version-element Element el = metainfFile.getRootElement().getChild(ELEMENT_IMAGE_PATH); // check whether it's null or not if (null == el || el.getText().trim().isEmpty()) { return null; } // else return the attribute value return new File(el.getText()); } /** * Ssaves the user defined path to images * @param path the user defined path to images */ public void setUserImagePath(String path) { // retrieve version-element Element el = metainfFile.getRootElement().getChild(ELEMENT_IMAGE_PATH); // check whether it's null or not if (null == el) { // than create an empty atachment-path and add it el = new Element(ELEMENT_IMAGE_PATH); metainfFile.getRootElement().addContent(el); } // check for valid parameter if (path != null) { // set new path el.setText(path); // and change modified state setMetaModified(true); } } /** * This method returns the current (latest) version of the fileformat. This may differ from * the version of the <i>loaded</i> file. see {@link #getVersionInfo() getVersionInfo()} to retrieve * the version-number of the loaded fileformat. * * @return a string containing the current (latest) version-number of the zettelkasten-file-format */ public String getCurrentVersionInfo() { // return the current version info return currentVersion; } /** * Set and Get the modified state of the meta-information * @return */ public boolean isMetaModified() { return metamodified; } /** * Set and Get the modified state of the meta-information * @param m */ public void setMetaModified(boolean m) { metamodified = m; zknframe.setBackupNecessary(); } /** * Set and Get the modified state of the file * @return */ public boolean isModified() { return modified; } /** * Set and Get the modified state of the file * @param m */ public void setModified(boolean m) { modified = m; zknframe.setBackupNecessary(); } /** * Returns the size of this list. This list stores the xml-files which * should be retrieved from the compressed main-datafile. See class * CLoadDialog.java for more details. * @return the amount of files to load from the main datafile */ public int getFilesToLoadCount() { return filesToLoad.size(); } /** * Returns the filename of the xml-datafiles we want to retrieve from our * compressed main-datafile. See class CLoadDialog.java for more details. * @param index (the element which should be retrieved) * @return (the string containing the filename of the xml-file we want to have) */ public String getFileToLoad(int index) { return filesToLoad.get(index); } /** * Set and Get the whole main data * (Zettelkasten only, without Author and Keyword lists) * @param zkd (zettelkasten xml datafile) */ public void setZknData(Document zkd) { zknFile = zkd; setModified(true); } /** * Set and Get the whole main data * (Zettelkasten only, without Author and Keyword lists) * @return zettelkasten xml datafile */ public Document getZknData() { return zknFile; } /** * This method checks whether the current fileformat is of a <b>newer</b> version than the loaded data-file. * if so, we have to convert the data into the new fileformat. use {@link #getVersionInfo() getVersionInfo()} * and {@link #getCurrentVersionInfo() getCurrentVersionInfo()} to retrieve the version numbers of the loaded * and current file-format.<br><br> * <b>Important!</b> Use {@link #updateVersionInfo() updateVersionInfo()} to update the version-setting of the * loaded data-file. * * @return {@code true} if the current, latest file-format is of a newer version than the loaded data-file. * {@code false} if the loaded data-file is uptodate. */ public boolean isNewVersion() { // get version info String verinfo = getVersionInfo(); // check for valid value if (verinfo != null && !verinfo.isEmpty()) { // get data-version of loaded file float lv = Float.parseFloat(verinfo); // get current fileversion float cv = Float.parseFloat(currentVersion); // check whether the current data-version is newer than the loaded one return (lv < cv); } // log error Constants.zknlogger.log(Level.WARNING, "Check for new file version failed. Could not read version info!"); return false; } /** * This method checks whether the current fileformat is of an <b>older</b> version than the loaded data-file. * This might be the case, if the loaded data-file was saved with a newer program-version than the currently * used program.<br><br> * If so, we have to tell the user that the file-format is not supported and cannot be opened with the * current program-version. * * @return {@code true} if the current program-version cannot read the loaded data-file because it was saved * with a newer program-version. {@code false} if the loaded data-file can be read. */ public boolean isIncompatibleFile() { // get version info String verinfo = getVersionInfo(); // check for valid value if (verinfo != null && !verinfo.isEmpty()) { // get data-version of loaded file float lv = Float.parseFloat(verinfo); // get current fileversion float cv = Float.parseFloat(currentVersion); // check whether the current data-version is newer than the loaded one return (lv > cv); } // log error Constants.zknlogger.log(Level.WARNING, "Could not check for data compatibility. File version could not be read!"); return false; } /** * This method appends a document with zettelkasten-data to an existing * document.<br><br> * This method is used when importing data. The imported data is appended * to an existing, opened data file. * * @param zkd the zettelkasten-data in xml-document-format */ public void appendZknData(Document zkd) { // get current count, so we know at which position new entry were added // to the XML-document int currentpos = getCount(ZKNCOUNT); // create zettel element Element zettel; // dummy variable boolean mod = false; // remove each element. we need to use the remove-content-method, because // this detached the element from its former parent, so it can be added // to the new main-data-file // check whether we have any content left... while (zkd.getRootElement().getContentSize() > 0) { // try to remove/detach the child-element if ((zettel = (Element) zkd.getRootElement().removeContent(0)) != null) { // check whether the imported entry is empty or not if (zettel.getChild(ELEMENT_CONTENT) != null && !zettel.getChild(ELEMENT_CONTENT).getText().isEmpty()) { try { zknFile.getRootElement().addContent(zettel); // set modified flag mod = true; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } } } } // check whether we have added any new entries // if so, re-convert IDs to numbers if (mod) { // go through all new added entries for (int cnt = currentpos + 1; cnt <= getCount(ZKNCOUNT); cnt++) { // retrieve each new added entry zettel = retrieveZettel(cnt); // set back reference to current last entry setPrevZettel(cnt, getLastZettel()); // set pointer from current last entry to this new imported/added entry setNextZettel(getLastZettel(), cnt); // set pointer from first entry to this entry setPrevZettel(getFirstZettel(), cnt); // set next pointer from this entry to first entry setNextZettel(cnt, getFirstZettel()); // set this entry as new last entry setLastZettel(cnt); // // here we change the entry's luhmann-numbers (trailing entries) and the // entry's manual links with the unique IDs // replaceAttributeIDWithNr(zettel); // // here we convert back the author IDs in footnote references // to the related author index numbers... // // retrieve content of entry and convert all author footnotes, which // contain author-index-numbers, into the related author-IDs. String content = zettel.getChild(ELEMENT_CONTENT).getText(); // check for footnotes int pos = 0; while (pos != -1) { // find the html-tag for the footnote pos = content.indexOf(Constants.FORMAT_FOOTNOTE_OPEN, pos); // if we found something... if (pos != -1) { // find the closing quotes int end = content.indexOf("]", pos + 2); // if we found that as well... if (end != -1) { try { // extract footnote-number String fn = content.substring(pos + 4, end); // retrieve author ID from related footnote number try { int authorNr = getAuthorNumberFromID(fn); // replace author number with author ID inside footnote content = content.substring(0, pos + 4) + String.valueOf(authorNr) + content.substring(end); } catch (NumberFormatException ex) { // log error Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); Constants.zknlogger.log(Level.WARNING, "Could not convert author ID into author number!"); } } catch (IndexOutOfBoundsException ex) { } // and add it to the linked list, if it doesn't already exist // set pos to new position pos = end; } else { pos = pos + 4; } } } // check for manual links pos = 0; while (pos != -1) { // find the html-tag for the manual link pos = content.indexOf(Constants.FORMAT_MANLINK_OPEN, pos); // if we found something... if (pos != -1) { // find the closing quotes int end = content.indexOf("]", pos + 2); // if we found that as well... if (end != -1) { try { // extract manual-link--number String ml = content.substring(pos + 3, end); // retrieve entry ID from related manual link number try { int zetNr = getZettelNumberFromID(ml); // replace author number with author ID inside footnote content = content.substring(0, pos + 3) + String.valueOf(zetNr) + content.substring(end); } catch (NumberFormatException ex) { // log error Constants.zknlogger.log(Level.WARNING, "Could not convert entry ID into related manual link number!"); } } catch (IndexOutOfBoundsException ex) { } // and add it to the linked list, if it doesn't already exist // set pos to new position pos = end; } else { pos = pos + 3; } } } // set back changes zettel.getChild(ELEMENT_CONTENT).setText(content); } } // change modified state if (mod) { setModified(true); } } /** * Set and Get the authorlist * @param ald authorlist xml datafile */ public void setAuthorData(Document ald) { authorFile = ald; setModified(true); } /** * Set and Get the authorlist * @return authorlist xml datafile */ public Document getAuthorData() { return authorFile; } /** * Set and Get the keyword list * @param kld keyword xml datafile */ public void setKeywordData(Document kld) { keywordFile = kld; setModified(true); } /** * This method returns the keyword data file as JDOM document * @return the keyword data file as JDOM document */ public Document getKeywordData() { return keywordFile; } /** * Set and Get the metainformation of the zettelkasten-data * @param mid metainformation xml datafile */ public void setMetaInformationData(Document mid) { metainfFile = mid; setMetaModified(true); } /** * This method returns the metainformation of the zettelkasten-data as JDOM document * @return the metainformation as JDOM document */ public Document getMetaInformationData() { return metainfFile; } /** * Set the whole zettelkasten * (Zettelkasten with Author and Keyword lists) * * @param zkd a zettelkasten xml datafile * @param ald an authorlist xml datafile * @param kld a keywordlist xml datafile * @param mid */ public void setCompleteZknData(Document zkd, Document ald, Document kld, Document mid) { zknFile = zkd; authorFile = ald; keywordFile = kld; metainfFile = mid; setModified(true); } /** * This method returns the description of the zettelkasten-data, which is stored * in the metainformation-file of the zipped data-file. Usually needed when showing * information on the opened datafile * * @return a string with the description of this zettelkasten */ public String getZknDescription() { // get the child element Element el = metainfFile.getRootElement().getChild(ELEMEMT_DESCRIPTION); // check whether it's null if (null == el) { return ""; } // else return element-text return el.getText(); } /** * This method sets the description of the zettelkasten-data, which is stored * in the metainformation-file of the zipped data-file. * * @param desc a string with the description of this zettelkasten * @return */ public boolean setZknDescription(String desc) { // get the element Element el = metainfFile.getRootElement().getChild(ELEMEMT_DESCRIPTION); try { // check whether element exists if (null == el) { // if element does not exist, create it el = new Element(ELEMEMT_DESCRIPTION); // and add it to the meta-xml-file metainfFile.getRootElement().addContent(el); } // finally, set the text el.setText(desc); // change modified state setMetaModified(true); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } // return success return true; } /** * This method adds another description of zettelkasten-data to the existing one. * Usually this is need when appending Zettelkasten-datafiles. * * @param desc */ public void addZknDescription(String desc) { // if description is not empty, concatenate it to old description if (!desc.isEmpty()) { if (setZknDescription(getZknDescription() + System.getProperty("line.separator") + System.getProperty("line.separator") + desc)) { setMetaModified(true); } } } /** * This method changes the frequencies of an entry's authors and keywords by the given value {@code addvalue}. * @param nr the entrynumber, which author- and keywords-frequencies should be changed * @param addvalue the amount of increase or decrease of each author/keyword-frequency */ private void changeFrequencies(int nr, int addvalue) { // first of all, we duplicate all authors and keywords frequencies from the existing entry. // therefore, we first retrieve all author-index-numbers from that entry int[] aus = getAuthorIndexNumbers(nr); // check whether we have any values at all if (aus != null && aus.length > 0) { // iterate the array for (int a : aus) { // check for valid value if (a != -1) { try { // retrieve existing author Element au = retrieveElement(authorFile, a); // chek for valid value if (au != null) { // get the count-value, which indicates the frequency of occurences of this // author in the whole data file String freq = au.getAttributeValue(ATTRIBUTE_FREQUENCIES); if (freq != null) { int f = Integer.parseInt(freq); // increase frequency by 1 au.setAttribute(ATTRIBUTE_FREQUENCIES, String.valueOf(f + addvalue)); } } } catch (NumberFormatException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } } } } // now do this for the keywords. retrieve all keyword -index-numbers from that entry int[] kws = getKeywordIndexNumbers(nr); // check whether we have any values at all if (kws != null && kws.length > 0) { // iterate the array for (int k : kws) { // check for valid value if (k != -1) { try { // retrieve existing author Element kw = retrieveElement(keywordFile, k); // chek for valid value if (kw != null) { // get the count-value, which indicates the frequency of occurences of this // keyword in the whole data file String freq = kw.getAttributeValue(ATTRIBUTE_FREQUENCIES); if (freq != null) { int f = Integer.parseInt(freq); // increase frequency by 1 kw.setAttribute(ATTRIBUTE_FREQUENCIES, String.valueOf(f + addvalue)); } } } catch (NumberFormatException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } } } } } /** * This method duplicates an entry and inserts it at the end or the next empty place in the * data file * * @param nr the number of the entry that should be duplicated * @return */ public boolean duplicateEntry(int nr) { // first of all, we duplicate all authors and keywords frequencies from the existing entry. // therefore, we first retrieve all author-index-numbers from that entry changeFrequencies(nr, 1); // retrieve entry that should be duplicated Element oldzettel = retrieveElement(zknFile, nr); // create new zettel Element zettel = new Element(ELEMENT_ZETTEL); // check whether we have any empty elements in between where we can insert the new entry int emptypos = retrieveFirstEmptyEntry(); // if we have any empty elements... if (emptypos != -1 && settings.getInsertNewEntryAtEmpty()) { // retrieve empty element zettel = retrieveElement(zknFile, emptypos); // and remove former content, so we can add new content zettel.removeContent(); } try { setZettelID(zettel); // // add title // // create child element with title information Element t = new Element(ELEMENT_TITLE); // and add it to the zettel-element zettel.addContent(t); // set value of the child element t.setText(oldzettel.getChild(ELEMENT_TITLE).getText()); // // add content // // create child element with content information Element c = new Element(ELEMENT_CONTENT); // and add it to the zettel-element zettel.addContent(c); // set value of the content element c.setText(oldzettel.getChild(ELEMENT_CONTENT).getText()); // // add author // // create child element with author information Element a = new Element(ELEMENT_AUTHOR); // and add it to the zettel-element zettel.addContent(a); // set value of author element a.setText(oldzettel.getChild(ELEMENT_AUTHOR).getText()); // // add keywords // // create child element with keyword information Element k = new Element(ELEMENT_KEYWORD); // and add it to the zettel-element zettel.addContent(k); // store keyword index numbers k.setText(oldzettel.getChild(ELEMENT_KEYWORD).getText()); // // now comes the manual links to other entries // Element m = new Element(ELEMENT_MANLINKS); zettel.addContent(m); m.setText(""); // // add hyperlinks // // create child element with link information Element h = new Element(ELEMENT_ATTACHMENTS); // and add it to the zettel-element zettel.addContent(h); // add each hyperlink. therefor, iterate the array List<Element> links = oldzettel.getChild(ELEMENT_ATTACHMENTS).getChildren(); Iterator<Element> i = links.iterator(); while (i.hasNext()) { // create a new subchuld-element Element sublink = new Element(ELEMENT_ATTCHILD); Element le = i.next(); // and add the link-string from the array sublink.setText(le.getText()); h.addContent(sublink); } // // add remarks // // create child element with content information Element r = new Element(ELEMENT_REMARKS); // and add it to the zettel-element zettel.addContent(r); // set value of the content element r.setText(oldzettel.getChild(ELEMENT_REMARKS).getText()); // // add timestamp // // set creation timestamp, but set no text for edit timestamp // since the entry is not edited setTimestamp(zettel, Tools.getTimeStamp(), ""); // // now comes the luhmann number // Element l = new Element(ELEMENT_TRAILS); zettel.addContent(l); l.setText(oldzettel.getChild(ELEMENT_TRAILS).getText()); // // complete datafile // // if we have any empty elements, go on here if (emptypos != -1 && settings.getInsertNewEntryAtEmpty()) { // return the empty-position, which is now filled with the new author-value zettelPos = emptypos; } else { // finally, add the whole element to the data file zknFile.getRootElement().addContent(zettel); // set the zettel-position to the new entry zettelPos = getCount(ZKNCOUNT); } // duplicate this entry into the correct entry order // by changing the prev/nex references (or pointers) of the entries. changeZettelPointer(zettelPos, nr); // titles have to be updated. setTitlelistUpToDate(false); // set modified state setModified(true); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } return true; } /** * This function retrieves an element of a xml document at a given * position. used for other methods like getAuthor or * getKeyword.<br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount()} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param doc the xml document where to look for elements. use following parameters:<br> * - {@link #authorFile authorFile}<br> * - {@link #keywordFile keywordFile}<br> * - {@link #zknFile zknFile} * @param pos the position of the element. must be a value from <b>1</b> to * {@link #getCount(int) getCount()}. * @return the element if a match was found, otherwise {@code null} */ private Element retrieveElement(Document doc, int pos) { // create a list of all elements from the given xml file try { List<?> elementList = doc.getRootElement().getContent(); // and return the requestet Element try { return (Element) elementList.get(pos - 1); } catch (IndexOutOfBoundsException e) { return null; } } catch (IllegalStateException e) { Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); return null; } } /** * This method updates the version-information of the loaded file to the latest version number. */ public void updateVersionInfo() { // retrieve version-element Element el = metainfFile.getRootElement().getChild(ELEMENT_VERSION_INFO); // check whether it's null or not if (el != null) { el.setAttribute("id", currentVersion); } } /** * This function retrieves an element of a xml document at a given * position. used for the export of entries, for instance. * <br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(ZKNCOUNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param pos the position of the element, ranged from 1 to {@link #getCount(int) getCount(ZKNCOUNT)} * @return the element if a match was found, otherwise {@code null} */ public Element retrieveZettel(int pos) { return retrieveElement(zknFile, pos); } /** * This method returns the position of a keyword in the keyword XML file {@link #keywordFile}. * if the keyword doesn't exist, the return value is {@code -1}. * * @param kw keyword which is searched for in the keyword list * @return the position of the author string or -1 if no match was found */ public int findKeywordInDatabase(String kw) { return getKeywordPosition(kw, true); } /** * This method returns the position of a keyword in the keyword XML file {@link #keywordFile}. * if the keyword doesn't exist, the return value is {@code -1}. * * @param kw keyword which is searched for in the keyword list * @param matchcase whether the keyword-search is case-sensitive ({@code true}) or not ({@code false}) * @return the position of the author string or -1 if no match was found */ public int getKeywordPosition(String kw, boolean matchcase) { // check for valid value if (null == kw || kw.trim().isEmpty()) return -1; // create a list of all keyword elements from the keyword xml file try { List<?> keywordList = keywordFile.getRootElement().getContent(); // and an iterator for the loop below Iterator<?> iterator = keywordList.iterator(); // counter for the return value if a found author matches the parameter int cnt = 1; // iterate loop while (iterator.hasNext()) { // retrieve each single element Element keyword = (Element) iterator.next(); // if keyword matches the parameter string, return the position if (matchcase && kw.equals(keyword.getText())) { return cnt; } else if (!matchcase && kw.equalsIgnoreCase(keyword.getText())) { return cnt; } // else increase counter cnt++; } // if no keyword was found, return -1 return -1; } catch (IllegalStateException e) { Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); return -1; } } /** * This method returns the author-index-number of that author-value, that contains the * bibkey (i.e. the "bibkey"-attribute) given in {@code bibkey}. * * @param bibkey the bibkey which position has to be found * @return the author-index-number (i.e. author-position in the authorFile.xml) of that author * which bibkey-attribute matches (case-sensitive!) the parameter {@code bibkey}, or {@code -1} if * no author with that bibkey-value was found. */ public int getBibkeyPosition(String bibkey) { // check for valid value if (null == bibkey || bibkey.trim().isEmpty()) return -1; // create a list of all author elements from the author xml file try { List<?> authorList = authorFile.getRootElement().getContent(); // and an iterator for the loop below Iterator<?> iterator = authorList.iterator(); // counter for the return value if a found author matches the parameter int cnt = 1; // iterate all author values while (iterator.hasNext()) { Element author = (Element) iterator.next(); // retrieve bibkey-attribute. since this attribute is optional, // "bk" also might be null! String bk = author.getAttributeValue(ATTRIBUTE_AUTHOR_BIBKEY); // if bibkey-attribute matches the parameter string, return the position if (bk != null && bk.equals(bibkey)) { return cnt; } // else increase counter cnt++; } // if no bibkey was found, return -1 return -1; } catch (IllegalStateException e) { Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); return -1; } } /** * This method adds a new keyword item to the keyword xml datafile * * @param kw the keyword which should be added * @param freq the new frequency of the keyword, or - if keyword already exists, e.g. in case * of merging entries or adding existing keywords to an entry - the increasement-step of the * frequency-occurences of existing keywords. use "1" if a keyword is simply added to an entry, so * in case the keyword already exists, its frequency is increased by 1. * @return position of the recently added keyword, or -1 if keyword could not be added */ public int addKeyword(String kw, int freq) { // check for valid value if (null == kw || kw.isEmpty()) { return -1; } // trim leading and trailing spaces kw = kw.trim(); // if keyeord is empty, return if (kw.isEmpty()) { return -1; } // check whether author already exists int pos = getKeywordPosition(kw, false); // if keyword already exists, just increase counter if (pos != -1) { try { // retrieve existing author Element keyw = retrieveElement(keywordFile, pos); // get the count-value, which indicates the frequency of occurences of this // keywords in the whole data file int f = Integer.parseInt(keyw.getAttributeValue(ATTRIBUTE_FREQUENCIES)); // increase frequency by 1 // change timestamp attribute updateKeywordTimestampAndID(keyw, f + freq, Tools.getTimeStampWithMilliseconds(), null); // change modified state setModified(true); // and return keyword index-number return pos; } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return -1; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return -1; } } // check whether we have any empty elements in between where we can insert the keyword int emptypos = retrieveFirstEmptyElement(keywordFile); // if we have any empty elements, go on here if (emptypos != -1) { try { // retrieve empty element Element k = retrieveElement(keywordFile, emptypos); // set keyword string as new value k.setText(kw); // set frequency of occurences to 1 // set timestamp attribute // set ID attribute // but first, check the length of "kw", because we want max. 5 first chars of kw // in keyword id String kwid; try { kwid = kw.substring(0, 5); } catch (IndexOutOfBoundsException ex) { kwid = kw; } updateKeywordTimestampAndID(k, freq, Tools.getTimeStampWithMilliseconds(), String.valueOf(emptypos) + kwid + Tools.getTimeStampWithMilliseconds()); // change list-up-to-date-state setKeywordlistUpToDate(false); // change modified state setModified(true); // return the empty-position, which is now filled with the new keyword-value return emptypos; } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return -1; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return -1; } } // get the root element of the keyword xml datafile else { try { Element kwFile = keywordFile.getRootElement(); // create a new keyword element Element newKeyword = new Element(ELEMENT_ENTRY); // add the new keyword element to the keyword datafile try { kwFile.addContent(newKeyword); // and finally add the parameter (new keyword string) to the recently created // keyword element newKeyword.addContent(kw); // set frequency of occurences to 1 // set timestamp attribute // set ID attribute // but first, check the length of "kw", because we want max. 5 first chars of kw // in keyword id String kwid; try { kwid = kw.substring(0, 5); } catch (IndexOutOfBoundsException ex) { kwid = kw; } updateKeywordTimestampAndID(newKeyword, freq, Tools.getTimeStampWithMilliseconds(), String.valueOf(keywordFile.getRootElement().getContent().size()) + kwid + Tools.getTimeStampWithMilliseconds()); // change list-up-to-date-state setKeywordlistUpToDate(false); // change modified state setModified(true); } catch (IllegalAddException e) { // do nothing here Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } // return the new size of the keyword file, i.e. the keyword position of // the recently added keyword entry // // get a list with all entry-elements of the keyword data List<?> keywordList = keywordFile.getRootElement().getContent(); // and return the size of this list return keywordList.size(); } catch (IllegalStateException e) { Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); return -1; } } } /** * This method sets or changes the frequency-attribute, the timestamp-attribute and the ID-attribute of * either author- or keyword-elements. * * @param e The element, either an author-element (see {@link #authorFile}) or keyword-element (see {@link #keywordFile}) * @param attr_f the string value of the frequencies attribute name, usually use {@link #ATTRIBUTE_FREQUENCIES} here. * @param attr_ts the string value of the timestamp attribute name, use either {@link #ATTRIBUTE_AUTHOR_TIMESTAMP} or {@link #ATTRIBUTE_KEYWORD_TIMESTAMP} * @param attr_id the string value of the ID attribute name, use either {@link #ATTRIBUTE_AUTHOR_ID} or {@link #ATTRIBUTE_KEYWORD_ID} * @param freq the new frequency-value of the frequency-attribute. Use {@code -1} if you don't want to change this attribute value. * @param ts the new timestamp as string. use {@code null} as parameter if you don't want to change the timestamp attribute. * @param id the new ID as string. use {@code null} as parameter if you don't want to change the ID attribute. */ private void updateTimestampAndID(Element e, String attr_f, String attr_ts, String attr_id, int freq, String ts, String id) { // set frequency of occurences to 1 if (freq != -1) { e.setAttribute(attr_f, String.valueOf(freq)); } // set timestamp attribute if (attr_ts != null & !attr_ts.isEmpty() && ts != null) { e.setAttribute(attr_ts, ts); } // set ID attribute if (attr_id != null & !attr_id.isEmpty() && id != null) { e.setAttribute(attr_id, id); } } /** * This method sets or changes the frequency-attribute, the timestamp-attribute and the ID-attribute of * author-elements. * * @param e The author-element (see {@link #authorFile}) * @param freq the new frequency-value of the frequency-attribute. Use {@code -1} if you don't want to change this attribute value. * @param ts the new timestamp as string. use {@code null} as parameter if you don't want to change the timestamp attribute. * @param id the new ID as string. use {@code null} as parameter if you don't want to change the ID attribute. */ private void updateAuthorTimestampAndID(Element e, int freq, String ts, String id) { updateTimestampAndID(e, ATTRIBUTE_FREQUENCIES, ATTRIBUTE_AUTHOR_TIMESTAMP, ATTRIBUTE_AUTHOR_ID, freq, ts, id); } /** * This method sets or changes the frequency-attribute, the timestamp-attribute and the ID-attribute of * keyword-elements. * * @param e The keyword-element (see {@link #keywordFile}) * @param freq the new frequency-value of the frequency-attribute. Use {@code -1} if you don't want to change this attribute value. * @param ts the new timestamp as string. use {@code null} as parameter if you don't want to change the timestamp attribute. * @param id the new ID as string. use {@code null} as parameter if you don't want to change the ID attribute. */ private void updateKeywordTimestampAndID(Element e, int freq, String ts, String id) { updateTimestampAndID(e, ATTRIBUTE_FREQUENCIES, ATTRIBUTE_KEYWORD_TIMESTAMP, ATTRIBUTE_KEYWORD_ID, freq, ts, id); } /** * This method adds several keywords to the keyword xml datafile, without assigning them * to a certain entry * * @param kws the keywords which should be added */ public void addKeywordsToDatabase(String[] kws) { // if keyeord is empty, return if (null == kws || 0 == kws.length) { return; } // iterate all keywords for (String kw : kws) { // check whether keyword already exists int pos = getKeywordPosition(kw, false); // no, we have a new keyword. so add it... if (-1 == pos) { // check whether we have any empty elements in between where we can insert the keyword int emptypos = retrieveFirstEmptyElement(keywordFile); // if we have any empty elements, go on here if (emptypos != -1) { try { // retrieve empty element Element k = retrieveElement(keywordFile, emptypos); // set keyword string as new value k.setText(kw); // set frequency of occurences to 0 // set timestamp attribute // set ID attribute // but first, check the length of "kw", because we want max. 5 first chars of kw // in keyword id String kwid; try { kwid = kw.substring(0, 5); } catch (IndexOutOfBoundsException ex) { kwid = kw; } updateKeywordTimestampAndID(k, 0, Tools.getTimeStampWithMilliseconds(), String.valueOf(emptypos) + kwid + Tools.getTimeStampWithMilliseconds()); // change list-up-to-date-state setKeywordlistUpToDate(false); // change modified state setModified(true); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } } // get the root element of the keyword xml datafile else { Element kwFile = keywordFile.getRootElement(); // create a new keyword element Element newKeyword = new Element(ELEMENT_ENTRY); // add the new keyword element to the keyword datafile try { kwFile.addContent(newKeyword); // and finally add the parameter (new keyword string) to the recently created // keyword element newKeyword.addContent(kw); // set frequency of occurences to 0 // set timestamp attribute // set ID attribute // but first, check the length of "kw", because we want max. 5 first chars of kw // in keyword id String kwid; try { kwid = kw.substring(0, 5); } catch (IndexOutOfBoundsException ex) { kwid = kw; } updateKeywordTimestampAndID(newKeyword, 0, Tools.getTimeStampWithMilliseconds(), String.valueOf(keywordFile.getRootElement().getContent().size()) + kwid + Tools.getTimeStampWithMilliseconds()); // change list-up-to-date-state setKeywordlistUpToDate(false); // change modified state setModified(true); } catch (IllegalAddException e) { // do nothing here Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } } } } } /** * This methods returns the keyword of a given position in the <b>keyword-datafile</b>.<br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(KWCOUNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param pos a valid position of an element, ranged from 1 to {@link #getCount(int) getCount(KWCOUNT)} * @return the keyword string, or an empty string, if no such keyword exists */ public String getKeyword(int pos) { // retrieve the keyword element Element keyword = retrieveElement(keywordFile, pos); // return the matching string value of the keyword element String retval; // check whether element is null if (null == keyword) { retval = ""; } else { retval = keyword.getText(); } return retval; } /** * This method sets a keyword to a given position in the keyword datafile * could be used for overwriting/changing existing keywords * * @param pos the position of the keyword * @param kw the keyword string itself */ public void setKeyword(int pos, String kw) { // retrieve and store the old keyword that should be replaced by the new one. // we need this to check whether we also have to replace synonyms... String oldkeyword = getKeyword(pos); // create a list of all keyword elements from the keyword xml file try { // retrieve keyword Element keyword = retrieveElement(keywordFile, pos); // if a valid element was found... if (keyword != null) { // ...set the new text keyword.setText(kw); // find the oldkeyword in the synonymsfile... int synpos = synonymsObj.findSynonym(oldkeyword, true); // if we found a synonym, ask the user whether it also should be replaced if (synpos != -1) { // create a JOptionPane with yes/no/cancel options int option = JOptionPane.showConfirmDialog(zknframe.getFrame(), zknframe.getResourceMap().getString("replaceKeywordsInSynonymsMsg", oldkeyword, kw), zknframe.getResourceMap().getString("replaceKeywordsInSynonymsTitle"), JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE); // when the user applied to yes, we also change the synonym if (JOptionPane.YES_OPTION == option) { // get the synonymline String[] synline = synonymsObj.getSynonymLine(synpos, true); // go through all synonyms... if (synline != null && synline.length > 1) { for (int cnt = 0; cnt < synline.length; cnt++) { // ...and check whether the synonym-word equals the old keyword. if yes, replace // the synonym at that position with the new keyword if (synline[cnt].equals(oldkeyword)) { synline[cnt] = kw; } } } // finally, set back the synonyms. synonymsObj.setSynonymLine(synpos, synline); } } // and change the modified state of the file setModified(true); } } catch (IllegalStateException e) { // do nothing here Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); } } /** * This method sets a keyword to a given position in the keyword datafile * could be used for overwriting/changing existing keywords. * <br><br> * This method is only used to update a data file from an older data * version, see CUpdateVersion for more details... * * @param pos the position of the keyword * @param kw the keyword string itself * @param freq the frequency of the keyword */ public void setKeyword(int pos, String kw, int freq) { // create a list of all keyword elements from the keyword xml file try { // retrieve keyword Element keyword = retrieveElement(keywordFile, pos); // if a valid element was found... if (keyword != null) { try { // ...set the new text keyword.setText(kw); // and the frequency keyword.setAttribute(ATTRIBUTE_FREQUENCIES, String.valueOf(freq)); // and change the modified state of the file setModified(true); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } } } catch (IllegalStateException e) { // do nothing here Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); } } /** * This method searches the keyword-xml-file or author-xml-file for empty elements and returns the * number of the first empty element, if any. empty elements occur, when the user * deletes a keyword or author. in this case, to keep the permanent index-number of the other * keywords and authors, the keyword-/author-element is not completely removed, but only the text is * removed. * * @param the xml-document (either <i>keywordFile</i> or <i>authorFile</i>) * @return the number of the first empty element, or -1 if no empty element was found */ private int retrieveFirstEmptyElement(Document doc) { // create a list of all elements from the given xml file try { List<?> elementList = doc.getRootElement().getContent(); // and an iterator for the loop below Iterator<?> iterator = elementList.iterator(); // counter for the return value if a found author matches the parameter int cnt = 1; // iterare all elements while (iterator.hasNext()) { Element el = (Element) iterator.next(); // if author matches the parameter string, return the position if (el.getText().isEmpty()) { return cnt; } // else increase counter cnt++; } // if no author was found, return -1 return -1; } catch (IllegalStateException e) { Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); return -1; } } /** * This method searches the keyword-xml-file or author-xml-file for empty elements and returns the * number of the first empty element, if any. empty elements occur, when the user * deletes a keyword or author. in this case, to keep the permanent index-number of the other * keywords and authors, the keyword-/author-element is not completely removed, but only the text is * removed. * * @param the xml-document (either <i>keywordFile</i> or <i>authorFile</i>) * @return the number of the first empty element, or -1 if no empty element was found */ private int retrieveFirstEmptyEntry() { // create a list of all elements from the given xml file try { List<?> elementList = zknFile.getRootElement().getContent(); // and an iterator for the loop below Iterator<?> iterator = elementList.iterator(); // counter for the return value if a found author matches the parameter int cnt = 1; while (iterator.hasNext()) { Element el = (Element) iterator.next(); // if author matches the parameter string, return the position if (el.getChild(ELEMENT_TITLE).getText().isEmpty() && el.getChild(ELEMENT_CONTENT).getText().isEmpty() && el.getChild(ELEMENT_AUTHOR).getText().isEmpty()) { return cnt; } // else increase counter cnt++; } // if no author was found, return -1 return -1; } catch (IllegalStateException e) { Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); return -1; } } /** * This method deletes a keyword by removing the content from the element * inside of the keyword xml datafile. the element itself is kept and left * empty. this ensures that the order and numbering of a keyword never * changes. Since the zettelkasten datafile stores the index-numbers of the keywords * a changing in the position/order/numbering of the keyword datafile would lead * to corrupted keyword associations in the zettelkasten data file * * @param pos (position of keyword which should be deleted) */ public void deleteKeyword(int pos) { // check whether keyword exists... if (!getKeyword(pos).isEmpty()) { // ...delete its content // therefore, get the keyword's index-number as string (for comparison below) String nr = String.valueOf(pos); // create new string buffer StringBuilder newKw = new StringBuilder(""); // and delete this index-number from all entries for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // get each element Element zettel = retrieveElement(zknFile, cnt); // get the keyword-index-numbers String[] kws = zettel.getChild(ELEMENT_KEYWORD).getText().split(","); // reset buffer newKw.setLength(0); for (String kw : kws) { // if deleted value does not equal keyword-value, add it if (!kw.equals(nr)) { // append index-number newKw.append(kw); // and a seperator-comma newKw.append(","); } } // shorten the stringbuffer by one char, since we have a // superfluous comma char (see for-loop above) if (newKw.length() > 0) { newKw.setLength(newKw.length() - 1); } // now set the new keyword-index-numbers to the zettel zettel.getChild(ELEMENT_KEYWORD).setText(newKw.toString()); } // we don't want to remove the element itself, because this would lead // to changing index-numbers/element-position within the document. however, // a keyword should ever keep the same index-number. rather, we could fill // this "empty space" with new keywords, Element keyword = retrieveElement(keywordFile, pos); // if we have an element, go on if (keyword != null) { // delete text keyword.setText(""); // and reset attributes keyword.setAttribute(ATTRIBUTE_FREQUENCIES, "0"); keyword.setAttribute(ATTRIBUTE_KEYWORD_ID, ""); keyword.setAttribute(ATTRIBUTE_KEYWORD_TIMESTAMP, ""); } // and change modified state setModified(true); } } /** * This method deletes an entry at thegiven position. since an entry-index-number * should never change to ensure that each entry always keeps its index-number, * we don't completely remove the element from the xml-file. rather, we simply delete * the content by setting empty values, so we have an "empty" element. * * @param pos the position of the entry which should be deleted * @return {@code true} if entry was successfully deleted {@code false} if it could not be deleted * (because it already has been deleted before, or entry-element did not exist). */ public boolean deleteZettel(int pos) { // check whether entry has already been deleted if (isDeleted(pos)) { // log error Constants.zknlogger.log(Level.WARNING, "Could not delete entry {0}! Entry already has been deleted!", String.valueOf(pos)); return false; } // retrieve the entry-element at the given position Element zettel = retrieveElement(zknFile, pos); // if the entry-element exists... if (zettel != null) { // remove this entry from the visible order // therefore, the previous entry of this entry should point // to the next entry of this entry setNextZettel(getPrevZettel(pos), getNextZettel(pos)); // and the the next entry of this entry should point // to the previous entry of this entry setPrevZettel(getNextZettel(pos), getPrevZettel(pos)); // check whether deleted entry was first entry if (pos == getFirstZettel()) { setFirstZettel(getNextZettel(pos)); } // check whether deleted entry was last entry if (pos == getLastZettel()) { setLastZettel(getPrevZettel(pos)); } // change zettelcounter zettelPos = getNextZettel(pos); // check whether it's out of bounds if (zettelPos > getCount(ZKNCOUNT) || zettelPos == -1) { zettelPos = getFirstZettel(); } // change author-and keyword-frequencies changeFrequencies(pos, -1); // retrieve manual links, so we can delete the backlinks from other entries. // each manual link from this entry to other entries creates a "backlink" from // other entries to this one. if we delete the manual links from this entry, // all backlinks to this entry are removed. String[] manlinks = zettel.getChild(ELEMENT_MANLINKS).getText().split(","); // delete manual links deleteManualLinks(manlinks); // ...delete entry's attributes zettel.setAttribute(ATTRIBUTE_ZETTEL_ID, ""); zettel.setAttribute(ATTRIBUTE_RATINGCOUNT, ""); zettel.setAttribute(ATTRIBUTE_RATING, ""); zettel.setAttribute(ATTRIBUTE_NEXT_ZETTEL, ""); zettel.setAttribute(ATTRIBUTE_PREV_ZETTEL, ""); // ...delete entry's content zettel.getChild(ELEMENT_TITLE).setText(""); zettel.getChild(ELEMENT_CONTENT).setText(""); zettel.getChild(ELEMENT_AUTHOR).setText(""); zettel.getChild(ELEMENT_KEYWORD).setText(""); zettel.getChild(ELEMENT_MANLINKS).setText(""); zettel.getChild(ELEMENT_REMARKS).setText(""); zettel.getChild(ELEMENT_TRAILS).setText(""); zettel.getChild(ELEMENT_ATTACHMENTS).removeContent(); // zettel.getChild(ELEMENT_LUHMANN_NUMBER).setText(""); // remove timestamp by setting creation and last modification timestamp // to empty strings setTimestamp(zettel, "", ""); // and change modified state setModified(true); // update title list setTitlelistUpToDate(false); // return success return true; } // log error Constants.zknlogger.log(Level.WARNING, "Could not delete entry {0}! XML-element is null!", String.valueOf(pos)); return false; } /** * This method deletes certain authors from an entry's author-list. Therefore, * the to be deleted authors are passed as parameter. Then this method searches * the entry for occurences of these authors and deletes the index-numbers * of the to be deleted authors from the entry-author-indexnumbers. * * @param aus the to be deleted author values as strings * @param nr the number of the entry which authors should be deleted */ public void deleteAuthorsFromEntry(String[] aus, int nr) { // if we have any authors, go on... if (aus != null && aus.length > 0) { // get the entry's authors-index-numbers int[] entryaus = getAuthorIndexNumbers(nr); // check whether we have any author index numbers at all if ((entryaus != null) && (entryaus.length > 0)) { // init string buffer StringBuilder newau = new StringBuilder(""); // iterate array of entry-authors for (int cnt = 0; cnt < entryaus.length; cnt++) { // init found-indicator boolean found = false; for (String au : aus) { // get author index-number int pos = getAuthorPosition(au); // if an author, that should be deleted, is found, set found indicator to true if (pos == entryaus[cnt]) { found = true; } } // if no author, that should be deleted, was found... if (!found) { // append the author-index-number to the stringbuffer newau.append(String.valueOf(entryaus[cnt])); newau.append(","); } } // finally, cut off the last comma that we don't need. we only // have this comma, if we added at least on keyword-indexnumber if (newau.length() > 1) { newau.setLength(newau.length() - 1); } // set new index numbers setAuthorIndexNumbers(nr, newau.toString()); // we don't need to change modified state here, since this is done // in the setAuthorIndexNumbers-method // // but we have to change the frequency of occurences of authors, // what we do now... for (String a : aus) { // retrieve existing author Element au = retrieveElement(authorFile, getAuthorPosition(a)); // get the count-value, which indicates the frequency of occurences of this // author in the whole data file String freq = au.getAttributeValue(ATTRIBUTE_FREQUENCIES); if (freq != null) { int f = Integer.parseInt(freq); // decrease frequency by 1 au.setAttribute(ATTRIBUTE_FREQUENCIES, String.valueOf(f - 1)); } } } } } /** * This method deletes certain keywords from an entry's keyword-list. Therefore, * the to be deleted keywords are passed as parameter. Then this method searches * the entry for occurences of these keywords and deletes the index-numbers * of the to be deleted keywords from the entry-keyword-indexnumbers. * * @param kws the to be deleted keyword values as strings * @param nr the number of the entry which keywords should be deleted */ public void deleteKeywordsFromEntry(String[] kws, int nr) { // if we have any keywords, go on... if (kws != null && kws.length > 0) { // get the entry's keyword-index-numbers int[] entrykws = getKeywordIndexNumbers(nr); // check whether we have any keywords at all... if ((entrykws != null) && (entrykws.length > 0)) { // init string buffer StringBuilder newkw = new StringBuilder(""); // iterate array of entry-keywords for (int cnt = 0; cnt < entrykws.length; cnt++) { // init found-indicator boolean found = false; for (String kw : kws) { // get keyword index-number int pos = getKeywordPosition(kw, false); // if a keyword, that should be deleted, is found, set found indicator to true if (pos == entrykws[cnt]) { found = true; } } // if no keyword, that should be deleted, was found... if (!found) { // append the keyword-number to the stringbuffer newkw.append(String.valueOf(entrykws[cnt])); newkw.append(","); } } // finally, cut off the last comma that we don't need. we only // have this comma, if we added at least on keyword-indexnumber if (newkw.length() > 1) { newkw.setLength(newkw.length() - 1); } // set new index numbers setKeywordIndexNumbers(nr, newkw.toString()); // we don't need to change modified state here, since this is done // in the setKeywordIndexNumbers-method // // but we have to change the frequency of occurences of keywords, // what we do now... for (String k : kws) { // retrieve existing author Element kw = retrieveElement(keywordFile, getKeywordPosition(k, false)); // get the count-value, which indicates the frequency of occurences of this // author in the whole data file int f = Integer.parseInt(kw.getAttributeValue(ATTRIBUTE_FREQUENCIES)); // decrease frequency by 1 kw.setAttribute(ATTRIBUTE_FREQUENCIES, String.valueOf(f - 1)); } } } } /** * This methods searches the entry {@code nr} and looks for occurences of all keywords that * have been passed in the array {@code kws}. All keywords of this array that already exist * in the entry {@code nr} will be removed.<br><br> * After that, the user is asked to replace keywords, which appear as synonyms, with their * related index-words (which is recommended, since some functions look for synonyms according * to a given index-word; thus, an existing keyword that has synonyms should always appear as * so called <i>index-word</i>).<br><br> * Finally, a "cleaned" array of keywords that do not already exist in the entry are returned... * * @param kws the keywords as string-array that should be checked for whether it already exists or not * @param nr the index-number of the entry we want to know from whether it contains the keyword * @param matchcase whether the check-for-keywords shoould be case sensitive (true) or not (false) * @return a cleaned array that contains all keywords that are new to the entry, or {@code null} if * the array {@code kws} did not contain any new keywords. */ public String[] retrieveNonexistingKeywords(String[] kws, int nr, boolean matchcase) { // get the keyword-index-nunbers from the related entry int[] kwnr = getKeywordIndexNumbers(nr); // if we don't have any keywords, return the whole string-array with keywords which // was passed as parameter, since non of them can already exist in the current entry if ((null == kwnr) || (kwnr.length < 1)) { // copy linked list to array and ask the user if he wants to replace possible synonym-keywords // with their related index-words... return Tools.replaceSynonymsWithKeywords(synonymsObj, kws); } // create linked list that will contain all new keywords that don't already // exist in the entry "nr" List<String> cleanedKeywords = new ArrayList<String>(); // now check for the existence of each keyword in the entry "nr" and // add all non-existing (new) keywords to the linked list... for (String kw : kws) { if (!existsInKeywords(kw, nr, matchcase)) { cleanedKeywords.add(kw); } } // if we don't have any keywords left, return null... if (cleanedKeywords.size() < 1) { return null; } // copy linked list to array and ask the user if he wants to replace possible synonym-keywords // with their related index-words... kws = Tools.replaceSynonymsWithKeywords(synonymsObj, cleanedKeywords.toArray(new String[cleanedKeywords.size()])); // due to the replacement of keywords by their index-words we can again have double keywords now. // so we check for existing keywords in the new returned keyword-array again... if (kws != null && kws.length > 0) { // clear linked list cleanedKeywords.clear(); // check for the existence of each keyword in the entry "nr" and // add all non-existing (new) keywords to the linked list... for (String kw : kws) { if (!existsInKeywords(kw, nr, matchcase)) { cleanedKeywords.add(kw); } } // if we don't have any keywords left, return null... if (cleanedKeywords.size() < 1) { return null; } // else create new return-array kws = cleanedKeywords.toArray(new String[cleanedKeywords.size()]); } // return "cleaned" keyword-array. this array now contains only those keywords that are new to the entry // and which are not synonyms, but the related index-words... return kws; } /** * This methods searches the entry {@code nr} and looks for occurences of <b>all</b> the keyword * in the array {@code kws}. * If the entry contains all keywords of {@code kws}, this method returns true, i.e. the method * found out that the requested keyword already exists in the entry's keyword-list.<br><br> * <b>Attention!</b> In case you need to know whether a keyword exists in the <i>keyword.xml</i>-file * (i.e. the keywords-data-file), use the method {@link #getKeywordPosition(java.lang.String, boolean) getKeywordPosition()} * insted. * * @param kws the keywords which should be checked for whether they already exists or not * @param nr the index-number of the entry we want to know from whether it contains the keyword * @param log_and whether we have logical-and-search (<i>all</i> keywords of "kws" must exist * in that entry) or logical-or-search (<i>at least one</i> keyword of "kws" exists in entry. * @param matchcase whether the checking for keywords should be case-sensitive (true) or not (false) * @return {@code true} if the entry contains <b>all</b> keywords when we have logical-and-search, or * true when the entry contains <b>at least one</b> keyword when we have logical-or-search. * false otherwise */ public boolean existsInKeywords(String[] kws, int nr, boolean log_and, boolean matchcase) { // get the keyword-index-nunbers from the related entry int[] kwnr = getKeywordIndexNumbers(nr); // if we don't have any keywords, return false if ((null == kwnr) || (kwnr.length < 1)) { return false; } // a counter which indicates the amount of occurences int foundcount = 0; for (String kw : kws) { // reset found value boolean found = false; // get the keyword position (i.e. the index-number) of the passed parameter-string int pos = getKeywordPosition(kw, matchcase); // iterate the array of keyword-index-numbers of the target-entry // if the keyword we are looking for already exists, set found-value to true for (int loop : kwnr) { if (loop == pos) { found = true; } } // if we have found the keyword... if (found) { // either increase found-counter, when we have logical-and-combination if (log_and) { foundcount++; } // or simply return true when we have logical-or-search else { return true; } } } // if we found as much keywords as we have in the array, the // return-result should be true... return (foundcount == kws.length); } /** * This methods searches the entry {@code nr} and looks for occurences of the keyword {@code kw}. * If the entry contains the keyword {@code kw}, this method returns true, i.e. the method * found out that the requested keyword already exists in the entry's keyword-list.<br><br> * <b>Attention!</b> In case you need to know whether a keyword exists in the <i>keyword.xml</i>-file * (i.e. the keywords-data-file), use the method {@link #getKeywordPosition(java.lang.String, boolean) getKeywordPosition()} * instead. * * @param kw the keyword which should be checked for whether it already exists or not * @param nr the index-number of the entry we want to know from whether it contains the keyword * @param matchcase whether the check-for-keywords shoould be case sensitive (true) or not (false) * @return {@code true} if keyword already exists in the entry <i>nr</i>, false otherwise */ public boolean existsInKeywords(String kw, int nr, boolean matchcase) { // get the keyword-index-nunbers from the related entry int[] kwnr = getKeywordIndexNumbers(nr); // if we don't have any keywords, return false if ((null == kwnr) || (kwnr.length < 1)) { return false; } // get the keyword position (i.e. the index-number) of the passed parameter-string int pos = getKeywordPosition(kw, matchcase); // prepare return value boolean retval = false; // iterate the array of keyword-index-numbers of the target-entry // if the keyword we are looking for already exists, set return value to true for (int loop : kwnr) { if (loop == pos) { retval = true; } } return retval; } /** * This methods searches the entry {@code nr} and looks for occurences of the author {@code au}. * If the entry contains the author {@code au}, this method returns true, i.e. the method * found out that the requested author already exists in the entry's author-list.<br><br> * <b>Attention!</b> In case you need to know whether an author exists in the <i>author.xml</i>-file * (i.e. the authors-data-file), use the method {@link #getAuthorPosition(java.lang.String) getAuthorPosition()} * instead. * * @param au (the author which should be checked for whether it already exists or not) * @param nr (the index-number of the entry we want to know from whether it contains the author) * @return (true if author already exists, false otherwise) */ public boolean existsInAuthors(String au, int nr) { return existsInAuthors(getAuthorPosition(au), nr); } /** * This methods searches the entry {@code nr} and looks for occurences of the author {@code au}. * If the entry contains the author {@code au}, this method returns true, i.e. the method * found out that the requested author already exists in the entry's author-list.<br><br> * <b>Attention!</b> In case you need to know whether an author exists in the <i>author.xml</i>-file * (i.e. the authors-data-file), use the method {@link #getAuthorPosition(java.lang.String) getAuthorPosition()} * instead. * * @param authorindexnumber the author-index-number which should be checked for whether it already exists or not * @param nr the index-number of the entry we want to know from whether it contains the author * @return {@code true} if author already exists, {@code false} otherwise */ public boolean existsInAuthors(int authorindexnumber, int nr) { // get the author-index-nunbers from the related entry int[] aunr = getAuthorIndexNumbers(nr); // check whether we have any author index numbers at all if ((null == aunr) || (aunr.length < 1)) { return false; } // prepare return value boolean retval = false; // iterate the array of author-index-numbers of the target-entry // if the author-index-number we are looking for already exists, set return value to true for (int loop : aunr) { if (loop == authorindexnumber) { retval = true; } } return retval; } /** * This method merges two keywords. The method searches for the keyword "oldkw" in all entries. * If this keyword was found in an entry, the method then looks for the new keyword "newkw" in * that entry. If the new keyword also exists, nothing is changed. If the new keyword does not * exist, it is added to the entry. at the end, the old keyword is deleted from the datafile via * the method "deleteKeyword()" * * @param oldkw (the old keyword, which should be deleted) * @param newkw */ public void mergeKeywords(String oldkw, String newkw) { // get the position (i.e. index-number) of the old (to be deleted) keyword int oldpos = getKeywordPosition(oldkw, false); // get the position (i.e. index-number) of the new keyword // (into which the old keyword should be transformed) int newpos = getKeywordPosition(newkw, false); // check whether both exist if ((oldpos != -1) && (newpos != -1)) { // now go through the whole dataset for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // check, which of each entry contains the old keyword // now we have to add the new keyword-index-number, // if it does not already exist. we don't need to delete the *old* // index-number, because at the end we simply delete it with the // "deleteKeyword()" method. // if the new keyword doesn't exist, add it if (existsInKeywords(oldkw, cnt, false) && !existsInKeywords(newkw, cnt, false)) { addKeywordToEntry(newkw, cnt, 1); } } // finally, delete old keyword deleteKeyword(oldpos); } } /** * This method merges two authors. The method searches for the author "oldau" in all entries. * If this author was found in an entry, the method then looks for the new author "newau" in * that entry. If the new author also exists, nothing is changed. If the new author does not * exist, it is added to the entry. at the end, the old author is deleted from the datafile via * the method "deleteAuthor()" * * @param oldau (the old author, which should be deleted) * @param newau */ public void mergeAuthors(String oldau, String newau) { // get the position (i.e. index-number) of the old (to be deleted) author int oldpos = getAuthorPosition(oldau); // get the position (i.e. index-number) of the new author // (into which the old author should be transformed) int newpos = getAuthorPosition(newau); // check whether both exist if ((oldpos != -1) && (newpos != -1)) { // now go through the whole dataset for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // check, which of each entry contains the old author // now we have to add the new author-index-number, // if it does not already exist. we don't need to delete the *old* // index-number, because at the end we simply delete it with the // "deleteAuthor()" method. // if the new author doesn't exist, add it if (existsInAuthors(oldau, cnt) && !existsInAuthors(newau, cnt)) { addAuthorToEntry(newau, cnt, 1); } } // finally, delete old author deleteAuthor(oldpos); } } /** * This method adds a keyword, which is passed as string-parameter, to an existing * entry. the method first checks whether the entry {@code nr} already contains the keyword * {@code kw}. if not, the index-number of that keyword is retrieved and the new index-numbers * are added to the entry. * * @param kw the keyword which should be added to the entry * @param nr the index-number of the entry where the keyword should be added to * @param freq the new frequency of the keyword, or - if keyword already exists, e.g. in case * of merging entries or adding existing keywords to an entry - the increasement-step of the * frequency-occurences of existing keywords. use "1" if a keyword is simply added to an entry, so * in case the keyword already exists, its frequency is increased by 1. */ public void addKeywordToEntry(String kw, int nr, int freq) { // trim leading and trailing spaces kw = kw.trim(); // if keyword is empty, return if (kw.isEmpty()) { return; } // first check whether keyword already exists // return false, if keyword exists and we don't add it if (existsInKeywords(kw, nr, false)) { return; } // retrieve the current entry Element el = retrieveElement(zknFile, nr); // if we don't have a valid element, return false if (null == el || null == el.getChild(ELEMENT_KEYWORD)) { return; } // create empty stringbuffer StringBuilder sb = new StringBuilder(""); // append keywords sb.append(el.getChild(ELEMENT_KEYWORD).getText()); // append new separator, but only if we already have keywords if (sb.length() > 0) { sb.append(","); } // add it to the keyword-list int pos = addKeyword(kw, freq); // only proceed when success if (pos != -1) { // append index-number of the keyword which should be added sb.append(String.valueOf(pos)); // set the new keyword-index-numbers el.getChild(ELEMENT_KEYWORD).setText(sb.toString()); // finally, change modified state setModified(true); } } /** * This method adds several keywords, that are passed as string-array, to an existing * entry. the method first checks whether the entry {@code nr} already contains one of the * keywords given in the array {@code kws}. * if not, the index-number of that keyword is retrieved and the new index-numbers * are added to the entry. * * @param kws a string-array with keywords that should be added to the entry * @param nr the index-number of the entry where the keywords should be added to * @param freq the new frequency of the keywords, or - if any one of the keywords inside the array * {@code kws} already exists, e.g. in case * of merging entries or adding existing keywords to an entry - the increasement-step of the * frequency-occurences of existing keywords. use "1" if a keyword is simply added to an entry, so * in case the keyword already exists, its frequency is increased by 1. * @return */ public String[] addKeywordsToEntry(String[] kws, int nr, int freq) { // check for valid parameter. if not existing, return if (null == kws || kws.length < 1) return null; // clean keywords, i.e. retrieve only those keywords that are new // to the entry.... kws = retrieveNonexistingKeywords(kws, nr, false); // if we have any new keywords, go on here. if (kws != null && kws.length > 0) { // retrieve the current entry Element el = retrieveElement(zknFile, nr); // if we don't have a valid element, return false if (null == el || null == el.getChild(ELEMENT_KEYWORD)) return null; // create empty stringbuffer StringBuilder sb = new StringBuilder(""); // append keywords sb.append(el.getChild(ELEMENT_KEYWORD).getText()); // append new separator, but only if we already have keywords if (sb.length() > 0) sb.append(","); // go through all keywords... for (String kw : kws) { // trim leading and trailing spaces kw = kw.trim(); // if keyword is not empty and does not exist, go on if (!kw.isEmpty() && !existsInKeywords(kw, nr, false)) { // add it to the keyword-list int pos = addKeyword(kw, freq); // append index-number of the keyword which should be added if (pos != -1) sb.append(String.valueOf(pos)).append(","); } } // delete last comma if (sb.length() > 1) sb.setLength(sb.length() - 1); // set the new keyword-index-numbers el.getChild(ELEMENT_KEYWORD).setText(sb.toString()); // finally, change modified state setModified(true); } return kws; } /** * This method adds an author, which is passed as string-parameter, to an existing * entry. the method first checks whether the entry "nr" already contains the author * "au". if not, the index-number of that author is retrieved and the new index-numbers * are added to the entry. * * @param au the author which should be added to the entry * @param nr the index-number of the entry where the keyword should be added to * @param freq the new frequency of the author, or - if author already exists, e.g. in case * of merging entries or adding existing authors to an entry - the increasement-step of the * frequency-occurences of existing authors. use "1" if an author is simply added to an entry, so * in case the author already exists, its frequency is increased by 1. */ public void addAuthorToEntry(String au, int nr, int freq) { // trim leading and trailing spaces au = au.trim(); // if author is empty, return if (au.isEmpty()) return; // first check whether author already exists in that entry // return false, if author exists and we don't add it if (existsInAuthors(au, nr)) return; // retrieve the current entry Element el = retrieveElement(zknFile, nr); // if we don't have a valid element, return false if (null == el || null == el.getChild(ELEMENT_AUTHOR)) return; // create empty stringbuffer StringBuilder sb = new StringBuilder(""); // append author sb.append(el.getChild(ELEMENT_AUTHOR).getText()); // append new separator, but only if we already have authors if (sb.length() > 0) sb.append(","); // add it to the author-list int pos = addAuthor(au, freq); // only proceed when valid value if (pos != -1) { // append index-number of the author which should be added sb.append(String.valueOf(pos)); // set the new author-index-numbers el.getChild(ELEMENT_AUTHOR).setText(sb.toString()); // finally, change modified state setModified(true); } } /** * This method returns the position of an author in the author XML file {@link #authorFile}. * if the author doesn't exist, the return value is {@code -1}. * * @param auth author which is searched for in the author list * @return the position of the author string or -1 if no match was found */ public int findAuthorInDatabase(String auth) { return getAuthorPosition(auth); } /** * This method returns the position of an author in the author XML file {@link #authorFile}. * if the author doesn't exist, the return value is {@code -1}. * * @param auth author which is searched for in the author list * @return the position of the author string or -1 if no match was found */ public int getAuthorPosition(String auth) { // check for valid value if (null == auth || auth.trim().isEmpty()) { return -1; } // create a list of all author elements from the author xml file try { List<?> authorList = authorFile.getRootElement().getContent(); // and an iterator for the loop below Iterator<?> iterator = authorList.iterator(); // counter for the return value if a found author matches the parameter int cnt = 1; // iterate all author values while (iterator.hasNext()) { Element author = (Element) iterator.next(); // if author matches the parameter string, return the position if (auth.equalsIgnoreCase(author.getText())) return cnt; // else increase counter cnt++; } // if no author was found, return -1 return -1; } catch (IllegalStateException e) { return -1; } } /** * This method returns the position of an author in the author XML file, * which has the BibKey {@code bibkey} associated. * If no author with such BibKey exist, the return value is -1 * * @param bibkey the bibkey which is searched for in the author list * @return the position of the author string that contains that {@code bibkey}, * or -1 if no match was found */ public int getAuthorBibKeyPosition(String bibkey) { // check for valid parameter if (null == bibkey || bibkey.isEmpty()) return -1; // iterate all authors for (int cnt = 1; cnt <= getCount(AUCOUNT); cnt++) { // retrieve each author bibkey String aubib = getAuthorBibKey(cnt); // if author-value has a bibkey and this bibkey equals the bibkey-parameter, // then return the author-position if (aubib != null && aubib.equalsIgnoreCase(bibkey)) return cnt; } // nothing found, so return -1 return -1; } /** * This method adds a new author item to the author xml datafile * @param auth the author which should be added * @param freq the new frequency of the author, or - if author already exists, e.g. in case * of merging entries or adding existing authors to an entry - the increasement-step of the * frequency-occurences of existing authors. use "1" if an author is simply added to an entry, so * in case the author already exists, its frequency is increased by 1. * @return position of the recently added author, or -1 if author could not be added */ public int addAuthor(String auth, int freq) { // trim leading and trailing spaces auth = auth.trim(); // if author is empty, return if (auth.isEmpty()) return -1; // check whether author already exists int pos = getAuthorPosition(auth); // if author already exists, just increase counter if (pos != -1) { try { // retrieve existing author Element au = retrieveElement(authorFile, pos); // get the count-value, which indicates the frequency of occurences of this // author in the whole data file int f = Integer.parseInt(au.getAttributeValue(ATTRIBUTE_FREQUENCIES)); // increase frequency of occurences // change timestamp attribute updateAuthorTimestampAndID(au, f + freq, Tools.getTimeStampWithMilliseconds(), null); // change modified state setModified(true); // and return author index-number return pos; } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return -1; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return -1; } } // check whether we have any empty elements in between where we can insert the author int emptypos = retrieveFirstEmptyElement(authorFile); // if we have any empty elements, go on here if (emptypos != -1) { try { // retrieve empty element Element au = retrieveElement(authorFile, emptypos); // set author string as new value au.setText(auth); // set frequency of occurences to 1 // set timestamp attribute // set ID attribute // but first, check the length of "auth", because we want max. 5 first chars of auth // in author id String auid; try { auid = auth.substring(0, 5); } catch (IndexOutOfBoundsException ex) { auid = auth; } updateAuthorTimestampAndID(au, freq, Tools.getTimeStampWithMilliseconds(), String.valueOf(emptypos) + auid + Tools.getTimeStampWithMilliseconds()); // change list-up-to-date-state setAuthorlistUpToDate(false); // change modified state setModified(true); // return the empty-position, which is now filled with the new author-value return emptypos; } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return -1; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return -1; } } // get the root element of the author xml datafile else try { // get the root element of the author xml datafile Element authFile = authorFile.getRootElement(); // create a new author element Element newAuthor = new Element(ELEMENT_ENTRY); // add the new author element to the author datafile try { // add the new author element to the author datafile authFile.addContent(newAuthor); // and finally add the parameter (new author string) to the recently created // author element newAuthor.addContent(auth); // set frequency of occurences to 1 // set timestamp attribute // set ID attribute // but first, check the length of "auth", because we want max. 5 first chars of auth // in author id String auid; try { auid = auth.substring(0, 5); } catch (IndexOutOfBoundsException ex) { auid = auth; } updateAuthorTimestampAndID(newAuthor, freq, Tools.getTimeStampWithMilliseconds(), String.valueOf(authorFile.getRootElement().getContent().size()) + auid + Tools.getTimeStampWithMilliseconds()); // change list-up-to-date-state setAuthorlistUpToDate(false); // change modified state setModified(true); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } // return the new size of the author file, i.e. the author position of // the recently added author entry // get a list with all entry-elements of the author data List<?> authorList = authorFile.getRootElement().getContent(); // and return the size of this list return authorList.size(); } catch (IllegalStateException e) { Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); return -1; } } /** * This method adds a new entry to the datafile. The needed parameters come from the JDialog * "CNewEntry.java". This dialog opens an edit-mask so the user can input the necessary information. * If everything is done, the JDialog retrieves all the information as string(-array)-variables * and simply passes these as paramaters to this method. * <br> * <br> * What we have to do here is to check whether the keywords or links e.g. partly exist, and if so, * find out the related index number. Keywords which until now do not already exist in the keyword * file have to be added to the keyword file and the new index number has to be addes to the * keyword-element of the entry. and so on... * * @param title the entry's title as string * @param content the entry's content as string * @param authors the entry's author as string, retrieve index number and add it to the entry's author-element. * use {@code null} if no authors should be added * @param keywords the entry's keywords as string-array. retrieve index numbers and add those to the entry's keyword-element * use {@code null} if no authors should be added * @param remarks the remarks as string * @param links the entry's links as string * use {@code null} if no authors should be added * @param timestamp the current date. in this case, add it as creation date to the timestamp * @param luhmann the number of the currently display entry, before the user clicked "new" or "insert entry". * if we have to insert an entry, we need to know this number, because that entry retrieves this new entry's * index-number and adds it to its luhmann-tag (which indicates follower- and sub-entries). * use {@code -1} if no luhmann-number is needed (i.e. no follower-entry is added). * @param editDeletedEntry use {@code true} if user edits an deleted entry, which is the same as inserting a * new entry at the deleted entry's position. use {@code false} if a entry is added normally. * @param editDeletedEntryPosition the position of the currently displayed entry that is deleted and should be * overwritten with a new entry ({@code editDeletedEntry} is set to true). * @param insertAfterEntry indicates the position, after which existing entry the new added entry should be inserted. * Use {@code -1} to add the new entry to the end of the database. * @return one of the following constants:<br> * {@link #ADD_ENTRY_OK ADD_ENTRY_OK} if a normal entry was successfully added<br> * {@link #ADD_LUHMANNENTRY_OK ADD_LUHMANNENTRY_OK} if a follower-entry (trailing entry) was successfully added<br> * {@link #ADD_ENTRY_ERR ADD_ENTRY_ERR} if an error occured when adding a normal entry<br> * {@link #ADD_LUHMANNENTRY_ERR ADD_LUHMANNENTRY_ERR} if an error occured when adding a follower-entry (trailing entry) */ public int addEntry(String title, String content, String[] authors, String[] keywords, String remarks, String[] links, String timestamp, int luhmann, boolean editDeletedEntry, int editDeletedEntryPosition, int insertAfterEntry) { // init return value int retval = ADD_ENTRY_OK; List<Integer> manlinks; // check for valid content. if we have any content, // replace Unicode-chars with UBB-tags if (content != null && !content.isEmpty()) { content = Tools.replaceUnicodeToUbb(content); } // create a new zettel-element Element zettel = new Element(ELEMENT_ZETTEL); // check whether we have any empty elements in between where we can insert the new entry int emptypos = (editDeletedEntry) ? editDeletedEntryPosition : retrieveFirstEmptyEntry(); // check whether user wants to edit an already deleted entry and insert a new one at // that position if (editDeletedEntry || (emptypos != -1 && settings.getInsertNewEntryAtEmpty())) { // retrieve empty element zettel = retrieveElement(zknFile, emptypos); // and remove former content, so we can add new content zettel.removeContent(); } try { // add unique ID setZettelID(zettel); // // add title // // create child element with title information Element t = new Element(ELEMENT_TITLE); // and add it to the zettel-element zettel.addContent(t); // set value of the child element t.setText(title); // // add content // // create child element with content information Element c = new Element(ELEMENT_CONTENT); // and add it to the zettel-element zettel.addContent(c); // set value of the content element c.setText(content); // then, create form-images createFormImagesFromContent(content); // // add author // // create child element with author information Element a = new Element(ELEMENT_AUTHOR); // and add it to the zettel-element zettel.addContent(a); // create empty string buffer which stores the index numbers // of the converted authors StringBuilder newau = new StringBuilder(""); // check whether we have authors at all if ((authors != null) && (authors.length > 0)) { // iterate the array and get the index number of each author string // if a author does not already exist, add it to the authorfile for (String aut : authors) { // trim leading and trailing spaces aut = aut.trim(); // only proceed for this entry, if it contains a value if (!aut.isEmpty()) { // add author int authorPos = addAuthor(aut, 1); // append the index number in the string buffer newau.append(String.valueOf(authorPos)); // separator for the the index numbers, since more authors // and thus more index numbers might be stored in the author element newau.append(","); } } // check whether we have any author-value at all... if (newau.length() > 0) { // shorten the stringbuffer by one char, since we have a // superfluous comma char (see for-loop above) newau.setLength(newau.length() - 1); // and say that author list is out of date setAuthorlistUpToDate(false); } } a.setText(newau.toString()); // // add keywords // // create child element with keyword information Element k = new Element(ELEMENT_KEYWORD); // and add it to the zettel-element zettel.addContent(k); // create empty string buffer which stores the index numbers // of the converted keywords StringBuilder newkw = new StringBuilder(""); // check whether we have keywords at all if ((keywords != null) && (keywords.length > 0)) { // iterate the array and get the index number of each keyword string // if a keyword does not already exist, add it to the keywordfile for (String keyw : keywords) { // trim leading and trailing spaces keyw = keyw.trim(); // only proceed for this entry, if it contains a value if (!keyw.isEmpty()) { // add it to the data file // and store the position of the new added keyword in the // variable keywordPos int keywordPos = addKeyword(keyw, 1); // append the index number in the string buffer newkw.append(String.valueOf(keywordPos)); // separator for the the index numbers, since more keywords // and thus more index numbers might be stored in the keyword element newkw.append(","); } } // check whether we have any keyword-values at all... if (newkw.length() > 0) { // shorten the stringbuffer by one char, since we have a // superfluous comma char (see for-loop above) newkw.setLength(newkw.length() - 1); // and say that author list is out of date setKeywordlistUpToDate(false); } } // store keyword index numbers k.setText(newkw.toString()); // // now comes the manual links to other entries // Element m = new Element(ELEMENT_MANLINKS); zettel.addContent(m); // check for manual links in content // and add them manlinks = extractManualLinksFromContent(content); m.setText(retrievePreparedManualLinksFromContent(manlinks)); // // add hyperlinks // // create child element with link information Element h = new Element(ELEMENT_ATTACHMENTS); // and add it to the zettel-element zettel.addContent(h); // add each hyperlink string if (links != null && links.length > 0) { // therefor, iterate the array for (String l : links) { // create a new subchuld-element Element sublink = new Element(ELEMENT_ATTCHILD); // and add the link-string from the array sublink.setText(l); h.addContent(sublink); } } // // add remarks // // create child element with content information Element r = new Element(ELEMENT_REMARKS); // and add it to the zettel-element zettel.addContent(r); // set value of the content element r.setText(remarks); // // add remarks // // set creation timestamp, but set no text for edit timestamp // since the entry is not edited setTimestamp(zettel, Tools.getTimeStamp(), ""); // // now comes the luhmann number // Element l = new Element(ELEMENT_TRAILS); zettel.addContent(l); l.setText(""); // // complete datafile // // if we have any empty elements, go on here if (emptypos != -1 && settings.getInsertNewEntryAtEmpty()) { // return the empty-position, which is now filled with the new author-value zettelPos = emptypos; } else { // finally, add the whole element to the data file zknFile.getRootElement().addContent(zettel); // set the zettel-position to the new entry zettelPos = getCount(ZKNCOUNT); } // and add the new position to the history... addToHistory(); // set modified state setModified(true); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return ADD_ENTRY_ERR; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return ADD_ENTRY_ERR; } // if we have a follower-number (insert-entry), we have to change the luhmann-tag // of the related entry (which number is passed in the luhmann-variable) if (luhmann != -1) { // try to add luhmann number if (addLuhmannNumber(luhmann, zettelPos)) { // if it was successfull, we can insert this entry // after the "parent" entry retval = ADD_LUHMANNENTRY_OK; // to do this, we need to change the "insertAfter" value insertAfterEntry = luhmann; } else { retval = ADD_LUHMANNENTRY_ERR; } } // check whether inserted entry position is already the last position in // the entry order // in this case, we can set the variable to -1, so it will automatically be // added to the end changeZettelPointer(zettelPos, insertAfterEntry); // set this entry as first entry if we do not have any // first entry yet... if (-1 == getFirstZettel()) setFirstZettel(zettelPos); // save ID of last added entry setLastAddedZettelID(zettel); // create back-references for manual links // we can do this here first, because we need // "zettelPos" as reference, which is not available earlier addManualLink(manlinks, zettelPos); // entry successfully added return retval; } /** * This method updates the "pointer" references from entries when a new * entry is added to the data base. In this case, the former last entry * now refers to this entry as last entry, while the first entry back-references * to the new added entry (and no longer to the former last entry). The same * applies when an entry is inserted in between two entries. * <br><br> * This method is used for instance in the * {@link #addEntry(java.lang.String, java.lang.String, java.lang.String[], java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, int, int) addEntry} * or {@link #duplicateEntry(int) duplicateEntry} methods. * * @param zettelPos the index-number of the entry that was added to the data base. * @param insertAfterEntry the position of the existing entry after which the new * entry should be added. use {@code -1} to add an entry to the end of the entry * order. */ private void changeZettelPointer(int zettelPos, int insertAfterEntry) { // check whether entry should be inserted at the end of the database if (insertAfterEntry == getLastZettel()) insertAfterEntry = -1; // check whether new entry should be added to end of entry order if (-1 == insertAfterEntry) { // get current last entry int cur_last = getLastZettel(); // if we have no last entry, this one seems to be the last entry if (-1 == cur_last) { // so fix cur_first value cur_last = zettelPos; } // change reference of last entry's next-link setNextZettel(cur_last, zettelPos); // get current first entry int cur_first = getFirstZettel(); // if we have no first entry, this one seems to be the first entry if (-1 == cur_first) { // so fix cur_first value cur_first = zettelPos; } // change reference of first entry's prev-link setPrevZettel(cur_first, zettelPos); // sort in entry into entry-order setNextZettel(zettelPos, cur_first); setPrevZettel(zettelPos, cur_last); // set new last entry setLastZettel(zettelPos); } // in this case, we add an entry "in between". else { // get next-link of insert-entry int next_entry = getNextZettel(insertAfterEntry); // change next-link to new added entry setNextZettel(insertAfterEntry, zettelPos); // set pre-link of new added entry to the entry after which // this new one has been added setPrevZettel(zettelPos, insertAfterEntry); // set former insertAfterEntry's next entry's prev-link // to this new added entry setPrevZettel(next_entry, zettelPos); // set next-link of added entry setNextZettel(zettelPos, next_entry); } } /** * This method adds a new entry to the datafile. The needed parameters come from the JDialog * "CNewEntry.java". This dialog opens an edit-mask so the user can input the necessary information. * If everything is done, the JDialog retrieves all the information as string(-array)-variables * and simply passes these as paramaters to this method. * <br> * <br> * What we have to do here is to check whether the keywords or links e.g. partly exist, and if so, * find out the related index number. Keywords which until now do not already exist in the keyword * file have to be added to the keyword file and the new index number has to be addes to the * keyword-element of the entry. and so on... * * @param title the entry's title as string * @param content the entry's content as string * @param authors the entry's author as string, retrieve index number and add it to the entry's author-element. * use {@code null} if no authors should be added * @param keywords the entry's keywords as string-array. retrieve index numbers and add those to the entry's keyword-element * use {@code null} if no authors should be added * @param remarks the remarks as string * @param links the entry's links as string * use {@code null} if no authors should be added * @param timestamp the current date. in this case, add it as creation date to the timestamp * @param luhmann the number of the currently display entry, before the user clicked "new" or "insert entry". * if we have to insert an entry, we need to know this number, because that entry retrieves this new entry's * index-number and adds it to its luhmann-tag (which indicates follower- and sub-entries). * use {@code -1} if no luhmann-number is needed (i.e. no follower-entry is added). * @param insertAfterEntry indicates the position, after which existing entry the new added entry should be inserted. * Use {@code -1} to add the new entry to the end of the database. * @return one of the following constants:<br> * {@link #ADD_ENTRY_OK ADD_ENTRY_OK} if a normal entry was successfully added<br> * {@link #ADD_LUHMANNENTRY_OK ADD_LUHMANNENTRY_OK} if a follower-entry (trailing entry) was successfully added<br> * {@link #ADD_ENTRY_ERR ADD_ENTRY_ERR} if an error occured when adding a normal entry<br> * {@link #ADD_LUHMANNENTRY_ERR ADD_LUHMANNENTRY_ERR} if an error occured when adding a follower-entry (trailing entry) */ public int addEntry(String title, String content, String[] authors, String[] keywords, String remarks, String[] links, String timestamp, int luhmann, int insertAfterEntry) { return addEntry(title, content, authors, keywords, remarks, links, timestamp, luhmann, false, -1, insertAfterEntry); } /** * This method adds a new entry to the datafile, from an importet bibtex-file. This is the * case, if a bibtex-entry has annotations or abstracts, and the user wants automatically * to create a new entry from that bibtex-entry. * <br><br> * In contrary ti the normal {@link #addEntry(java.lang.String, java.lang.String, java.lang.String[], java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, int) addEntry} method, * we use this one to set an additional xml-attribute to that entry (see {@link #setContentFromBibTexRemark(int) setContentFromBibTexRemark(int)}) * to indicate that this entry's content was added and automatically created from a bibtex-file. Thus, * we can later use this indication to update all entries' contents from a bibtex-file, if the user * wants to update them... * * @param title the entry's title as string * @param content the entry's content as string * @param authors the entry's author as string, retrieve index number and add it to the entry's author-element. * use {@code null} if no authors should be added * @param keywords the entry's keywords as string-array. retrieve index numbers and add those to the entry's keyword-element * use {@code null} if no authors should be added * @param timestamp the current date. in this case, add it as creation date to the timestamp * @return one of the following constants:<br> * {@link #ADD_ENTRY_OK ADD_ENTRY_OK} if a normal entry was successfully added<br> * {@link #ADD_LUHMANNENTRY_OK ADD_LUHMANNENTRY_OK} if a follower-entry (trailing entry) was successfully added<br> * {@link #ADD_ENTRY_ERR ADD_ENTRY_ERR} if an error occured when adding a normal entry<br> * {@link #ADD_LUHMANNENTRY_ERR ADD_LUHMANNENTRY_ERR} if an error occured when adding a follower-entry (trailing entry) */ public int addEntryFromBibTex(String title, String content, String[] authors, String[] keywords, String timestamp) { // add entry int succeeded = addEntry(title, content, authors, keywords, "", null, timestamp, -1, false, -1, -1); // if operation was successful... if (succeeded == ADD_ENTRY_OK || succeeded == ADD_LUHMANNENTRY_OK) { // ... set a remark to that entry that it was added from a bibtex-file // we might need this in case we want to update this entry from a revised bibtex-file later setContentFromBibTexRemark(zettelPos, true); } return succeeded; } /** * This method sets/adds an indicator to an entry so we know that his entry was automatically * created from a bibtex-file (annotation/abstract of a bibtex-entry). We might use this * to check which entries have been automatically created and can be updated, when the user * re-imports a bibtex-file in purpose to update the Zettelkasten-data. * * @param pos the entry's index-number. The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(ZKNCOUNT)} * @param val {@code true} if entry's content is from a bibtex-file, {@code false} otherwise * (typically not used, since you can use the * {@link #addEntry(java.lang.String, java.lang.String, java.lang.String[], java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, int) addEntry()} * method if you want to "normally" add an entry). */ public void setContentFromBibTexRemark(int pos, boolean val) { // retrieve requested entry Element zettel = retrieveElement(zknFile, pos); // if entry does not exist, leave if (null == zettel) return; // add attribute to indicate that this entry was importet // from a bibtex file zettel.setAttribute("fromBibTex", (val) ? "1" : "0"); } /** * This method checks whether an entry with the given index number {@code pos} * was automatically created from a bibtex file. if so, the XML element * contains an attribute {@code fromBibTex} with a value "1". * * @param pos the entry's index-number. The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(ZKNCOUNT)} * @return {@code true} when the entry was automatically created from a bibtex file, * {@code false} otherwise. */ public boolean isContentFromBibTex(int pos) { // retrieve requested entry Element zettel = retrieveElement(zknFile, pos); // if entry does not exist, leave if (null == zettel) return false; // retrieve indicator, which is stored in an attribute String isFromBibTex = zettel.getAttributeValue("fromBibTex"); // if attribute exists and its value equals 1, we know that this // entry was created from a bibtex file return (isFromBibTex != null && isFromBibTex.equals("1")); } /** * This method changed an existing entry in the datafile. The needed parameters come from the JDialog * "CNewEntry.java". This dialog opens an edit-mask so the user can input the necessary information. * If everything is done, the JDialog retrieves all the information as string(-array)-variables * and simply passes these as paramaters to this method. * <br> * <br> * What we have to do here is to check whether the keywords or links e.g. partly exist, and if so, * find out the related index number. Keywords which until now do not already exist in the keyword * file have to be added to the keyword file and the new index number has to be addes to the * keyword-element of the entry. and so on... * * @param title the entry's title as string * @param content the entry's content as string * @param authors the entry's authors as string-array, retrieve index number and add it to the entry's author-element * @param keywords the entry's keywords as string-array. retrieve index numbers and add those to the entry's keyword-element * @param remarks the remarks as string * @param links the entry's links as string * @param timestamp the current date. in this case, add it as edit date to the timestamp * @param entrynumber the number of the entry that should be changed. * @return */ public boolean changeEntry(String title, String content, String[] authors, String[] keywords, String remarks, String[] links, String timestamp, int entrynumber) { // create a new zettel-element Element zettel = retrieveElement(zknFile, entrynumber); // create dummy element Element child; // if no entry exists, quit if (null == zettel) return false; // first of all, we remove all authors and keywords from the existing entry // we do this to update the frequency of the authors and keywords, so when adding // authors/keywords to the data-file, which already belonged to the entry, we would // increase the frequency although those authors/keywords are not new changeFrequencies(entrynumber, -1); // then, create form-images createFormImagesFromContent(content); try { // // change title // // retrieve the element child = zettel.getChild(ELEMENT_TITLE); // if child-element doesn't exist, add it to the zettel if (null == child) { // create new child element child = new Element(ELEMENT_TITLE); // and add it zettel.addContent(child); } // set value of the child element child.setText(title); // // change content // // retrieve the element child = zettel.getChild(ELEMENT_CONTENT); // if child-element doesn't exist, add it to the zettel if (null == child) { // create new child element child = new Element(ELEMENT_CONTENT); // and add it zettel.addContent(child); } // set value of the child element child.setText(content); // // change author // // retrieve the element child = zettel.getChild(ELEMENT_AUTHOR); // if child-element doesn't exist, add it to the zettel if (null == child) { // create new child element child = new Element(ELEMENT_AUTHOR); // and add it zettel.addContent(child); } // create empty string buffer which stores the index numbers // of the converted authors StringBuilder newau = new StringBuilder(""); // check whether we have authors at all if ((authors != null) && (authors.length > 0)) { // iterate the array and get the index number of each author string // if a keauthoryword does not already exist, add it to the authorfile for (String aut : authors) { // trim leading and trailing spaces aut = aut.trim(); // only proceed for this entry, if it contains a value if (!aut.isEmpty()) { // add it to the data file // and store the position of the new added author in the // variable authorPos int authorPos = addAuthor(aut, 1); // append the index number in the string buffer newau.append(String.valueOf(authorPos)); // separator for the the index numbers, since more authors // and thus more index numbers might be stored in the author element newau.append(","); } } // shorten the stringbuffer by one char, since we have a // superfluous comma char (see for-loop above) if (newau.length() > 0) newau.setLength(newau.length() - 1); } // store author index numbers child.setText(newau.toString()); // // change keywords // // retrieve the element child = zettel.getChild(ELEMENT_KEYWORD); // if child-element doesn't exist, add it to the zettel if (null == child) { // create new child element child = new Element(ELEMENT_KEYWORD); // and add it zettel.addContent(child); } // create empty string buffer which stores the index numbers // of the converted keywords StringBuilder newkw = new StringBuilder(""); // check whether we have keywords at all if ((keywords != null) && (keywords.length > 0)) { // iterate the array and get the index number of each keyword string // if a keyword does not already exist, add it to the keywordfile for (String keyw : keywords) { // trim leading and trailing spaces keyw = keyw.trim(); // only proceed for this entry, if it contains a value if (!keyw.isEmpty()) { // add it to the data file // and store the position of the new added keyword in the // variable keywordPos int keywordPos = addKeyword(keyw, 1); // append the index number in the string buffer newkw.append(String.valueOf(keywordPos)); // separator for the the index numbers, since more keywords // and thus more index numbers might be stored in the keyword element newkw.append(","); } } // shorten the stringbuffer by one char, since we have a // superfluous comma char (see for-loop above) if (newkw.length() > 0) newkw.setLength(newkw.length() - 1); } // store keyword index numbers child.setText(newkw.toString()); // // change manual links // addManualLink(entrynumber, extractManualLinksFromContent(content)); // // change hyperlinks // // retrieve the element child = zettel.getChild(ELEMENT_ATTACHMENTS); // if child-element doesn't exist, add it to the zettel if (null == child) { // create new child element child = new Element(ELEMENT_ATTACHMENTS); // and add it zettel.addContent(child); } // first, remove all existing links, since they are completely // set again child.removeChildren(ELEMENT_ATTCHILD); // add each hyperlink string // therefor, iterate the array for (String l : links) { // create a new subchuld-element Element sublink = new Element(ELEMENT_ATTCHILD); // and add the link-string from the array sublink.setText(l); child.addContent(sublink); } // // change remarks // // retrieve the element child = zettel.getChild(ELEMENT_REMARKS); // if child-element doesn't exist, add it to the zettel if (null == child) { // create new child element child = new Element(ELEMENT_REMARKS); // and add it zettel.addContent(child); } // set value of the content element child.setText(remarks); // // change timestamp // setTimestampEdited(zettel, timestamp); // // we don't need any changes on the luhmann number or for // manual links here... // // update the current zettel-position zettelPos = entrynumber; // and add the new position to the history... addToHistory(); // set modified state setModified(true); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } return true; } /** * This method adds a new follower- or sub-entry index-number to an entry. Followers', or * sub-entries', index-numbers are stored in the luhmann-tag. * <br><br> * It is similar to a typical tree: we have one "parent"-entry and several child-entries * (sub-entries or followers). each of these child-elements can have their own child-elements again * (whereby the child-element itself is then again understood as "parent"-entry). * <br><br> * So, the Luhmann-numbers of an entry only have one subordinated level of sub-entries. the tree- * structure comes from those sub-entries, that might have their own sub-entries again. * * @param entry the entry where the related insert-entry-index-number should be added to * @param addvalue the index-number of the inserted entry * @return {@code true} if everything was ok, false if the addvalue already existed or if the entry * indicated by "addvalue" itself already contains the entry "entry". in this case, we would * have an infinitive loop, with entry A having a sub-entry B, and B having a sub-entry A again * and so on... */ public boolean addLuhmannNumber(int entry, int addvalue) { // check whether entry and addvalue are identical if (entry == addvalue) return false; // get the entry where the luhmann-number should be added to Element zettel = retrieveElement(zknFile, entry); // get the entry where the luhmann-number should be added to Element tobeadded = retrieveElement(zknFile, addvalue); // if entry does not exist, leave if (null == zettel || null == zettel.getChild(ELEMENT_TRAILS)) return false; // if entry does not exist, leave if (null == tobeadded) return false; // get the luhmann-numbers of that entry String lnr = zettel.getChild(ELEMENT_TRAILS).getText(); // check whether the addvalue already exists in that entry if (!lnr.isEmpty()) { // copy all values to an array String[] lnrs = lnr.split(","); // go throughh array of current luhmann-numbers for (String exist : lnrs) { try { // if addvalue exist, return false if (Integer.parseInt(exist) == addvalue) return false; } catch (NumberFormatException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } } } // now we have to check, whether the current entry is already existing // in the entry that index-number (addvalue) we want to add to the luhmann-numbers // if "entry" already exists in entry "addvalue"'s luhmann-tag, we would have // an infinite loop... // the problem is, that we here have to recursively check not only the "addvalue" // entry's luhmann-tag, but also each sub-entry that consists in the "addvalue" // entry's tag... lnr = tobeadded.getChild(ELEMENT_TRAILS).getText(); // check whether the addvalue already exists in that entry // if entry exists in the addvalue-entry luhmann-tag, or in any sub-entry // of the addvalue-entry, leave method to prevent infinite loops if (!lnr.isEmpty() && existsInLuhmann(addvalue, entry, false)) return false; // get the luhmann-numbers of that entry StringBuilder sb = new StringBuilder(zettel.getChild(ELEMENT_TRAILS).getText()); // append separator comma, but only if we already have values if (sb.length() > 0) sb.append(","); // append the addvalue sb.append(String.valueOf(addvalue)); /* // the the string buffer contains at least two values, we want to sort them if (sb.indexOf(",")!=-1) { // copy all values of the buffer to an string array String[] dummy = sb.toString().split(","); // create integer array, because when we sort a string-array, // the value "12" would be smaller than "5". int[] intdummy = new int[dummy.length]; // iterate array for (int cnt=0; cnt<intdummy.length; cnt++) { try { // convert all strings to integer intdummy[cnt] = Integer.parseInt(dummy[cnt]); } catch (NumberFormatException ex) { CConstants.zknlogger.log(Level.WARNING,ex.getLocalizedMessage()); } } // sort the array if (intdummy!=null && intdummy.length>0) Arrays.sort(intdummy); // reset the string buffer sb.setLength(0); // iterate the sorted array for (int cnt=0; cnt<intdummy.length; cnt++) { // and append all values to the string buffer sb.append(String.valueOf(intdummy[cnt])); sb.append(","); } // finallay, remove the last "," if (sb.length()>1) sb.setLength(sb.length()-1); } */ // and set the new string to the luhmann-tag zettel.getChild(ELEMENT_TRAILS).setText(sb.toString()); // addvalue was successfully added setModified(true); return true; } /** * This method adds a manual link to an entry. * * @param entry the entry where the referred entry-number should be added to * @param addvalue the index-number of the referred entry * @return {@code true} if everything was ok, false if the addvalue already existed or other errors occured. */ public boolean addManualLink(int entry, int addvalue) { // first, add current entry as manual link to the referred entry - we need this to // get double-links, from entry a to entry b and back from b to a. addManLink(addvalue, entry); // now add the referrer-entry-number to the current entry. return addManLink(entry, addvalue); } /** * * @param manlinks * @param sourceEntry */ public void addManualLink(List<Integer> manlinks, int sourceEntry) { if (manlinks != null && !manlinks.isEmpty()) { // iterate all manual references for (int mlcnt : manlinks) { // and add backreference to current new entry // to all referenced entries addManLink(mlcnt, sourceEntry); } } } /** * * @param sourceEntry * @param manlinks */ public void addManualLink(int sourceEntry, List<Integer> manlinks) { if (manlinks != null && !manlinks.isEmpty()) { // iterate all manual references for (int mlcnt : manlinks) { // and add backreference to current new entry // to all referenced entries addManualLink(mlcnt, sourceEntry); } } } /** * This method adds a manual link to an entry. It is called * from {@link #addManualLink(int, int) addManualLink}. * * @param entry the entry where the referred entry-number should be added to * @param addvalue the index-number of the referred entry * @return {@code true} if everything was ok, false if the addvalue already existed or other errors occured. */ private boolean addManLink(int entry, int addvalue) { // check whether entry and addvalue are identical if (entry == addvalue) return false; // get the entry where the luhmann-number should be added to Element zettel = retrieveElement(zknFile, entry); // if entry does not exist, leave if (null == zettel || null == zettel.getChild(ELEMENT_MANLINKS)) return false; // get the manual links of that entry String lnr = zettel.getChild(ELEMENT_MANLINKS).getText(); // check whether the addvalue already exists in that entry if (!lnr.isEmpty()) { // copy all values to an array String[] lnrs = lnr.split(","); // go throughh array of current luhmann-numbers for (String exist : lnrs) { try { // if addvalue exist, return false if (Integer.parseInt(exist) == addvalue) return false; } catch (NumberFormatException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } } } // get the luhmann-numbers of that entry StringBuilder sb = new StringBuilder(zettel.getChild(ELEMENT_MANLINKS).getText()); // append separator comma, but only if we already have values if (sb.length() > 0) sb.append(","); // append the addvalue sb.append(String.valueOf(addvalue)); // the the string buffer contains at least two values, we want to sort them if (sb.indexOf(",") != -1) { // copy all values of the buffer to an string array String[] dummy = sb.toString().split(","); // create integer array, because when we sort a string-array, // the value "12" would be smaller than "5". int[] intdummy = new int[dummy.length]; // iterate array for (int cnt = 0; cnt < intdummy.length; cnt++) { try { // convert all strings to integer intdummy[cnt] = Integer.parseInt(dummy[cnt]); } catch (NumberFormatException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } } // sort the array if (intdummy.length > 0) Arrays.sort(intdummy); // reset the string buffer sb.setLength(0); // iterate the sorted array for (int cnt = 0; cnt < intdummy.length; cnt++) { // and append all values to the string buffer sb.append(String.valueOf(intdummy[cnt])); sb.append(","); } // finallay, remove the last "," if (sb.length() > 1) sb.setLength(sb.length() - 1); } // and set the new string to the manlinks-tag zettel.getChild(ELEMENT_MANLINKS).setText(sb.toString()); // addvalue was successfully added setModified(true); return true; } /** * Removes a certain entry-number from the luhmann-numbers of an entry. * * @param entry the entry where a luhmann-number should be removed * @param removevalue the index-number that should be removed from "entry" */ public void deleteLuhmannNumber(int entry, int removevalue) { // check whether entry and removevalue are identical if (entry == removevalue) return; // get the entry where the luhmann-number should be added to Element zettel = retrieveElement(zknFile, entry); // if entry does not exist, leave if (null == zettel || null == zettel.getChild(ELEMENT_TRAILS)) return; // get the luhmann-numbers of that entry String lnr = zettel.getChild(ELEMENT_TRAILS).getText(); // check whether the addvalue already exists in that entry if (!lnr.isEmpty()) { // copy all values to an array String[] lnrs = lnr.split(","); // create new string buffer for the final values StringBuilder sb = new StringBuilder(""); // convert remove-value to string, so we can compare String removenr = String.valueOf(removevalue); // go through array of current luhmann-numbers for (String exist : lnrs) { // if the current luhhmann-number is not the one which should be deleted... if (!exist.equals(removenr)) { // ...add it to the buffer sb.append(exist); sb.append(","); } } // finally, remove trailing comma if (sb.length() > 1) sb.setLength(sb.length() - 1); // and set the new string to the luhmann-tag zettel.getChild(ELEMENT_TRAILS).setText(sb.toString()); // addvalue was successfully added setModified(true); } } /** * This method inserts the entry-number {@code insertnr} as luhmann-number at the position * {@code pos} within the entry's {@code entry} luhmann-numbers. * * @param entry the entry where the luhmann-number {@code insertnr} should be inserted * @param insertnr the number of the entry that should be added as luhmann-number * @param pos the position of the {@code insertnr}, i.e. at which position {@code insertnr} * should be added as luhmann-number * @return */ public boolean insertLuhmannNumber(int entry, int insertnr, int pos) { // check whether entry and removevalue are identical if (entry == insertnr) return false; // get the entry where the luhmann-number should be added to Element zettel = retrieveElement(zknFile, entry); // get the entry that should be added as luhmann-number Element tobeadded = retrieveElement(zknFile, insertnr); // if entry does not exist, leave if (null == zettel || null == zettel.getChild(ELEMENT_TRAILS)) return false; // get the luhmann-numbers of that entry String lnr = zettel.getChild(ELEMENT_TRAILS).getText(); // check whether the addvalue already exists in that entry if (!lnr.isEmpty()) { // now we have to check, whether the current entry is already existing // in the entry that index-number (addvalue) we want to add to the luhmann-numbers // if "entry" already exists in entry "addvalue"'s luhmann-tag, we would have // an infinite loop... // the problem is, that we here have to recursively check not only the "addvalue" // entry's luhmann-tag, but also each sub-entry that consists in the "addvalue" // entry's tag... String alnr = tobeadded.getChild(ELEMENT_TRAILS).getText(); // check whether the addvalue already exists in that entry // if entry exists in the addvalue-entry luhmann-tag, or in any sub-entry // of the addvalue-entry, leave method to prevent infinite loops if (!alnr.isEmpty() && existsInLuhmann(insertnr, entry, false)) return false; // copy all values to an array String[] lnrs = lnr.split(","); // create list List<String> luhmannnrs = new ArrayList<String>(); // copy all numbers to list, so we can insert the new number via this list // for (String ln : lnrs) luhmannnrs.add(ln); luhmannnrs.addAll(Arrays.asList(lnrs)); try { // now insert the new number luhmannnrs.add(pos, String.valueOf(insertnr)); } catch (IndexOutOfBoundsException e) { // if the index-number was out of bounds, append number to the end of the list luhmannnrs.add(String.valueOf(insertnr)); } // create stringbuilder StringBuilder sb = new StringBuilder(""); for (String luhmannnr : luhmannnrs) { sb.append(luhmannnr).append(","); } // finally, remove trailing comma if (sb.length() > 1) sb.setLength(sb.length() - 1); // and set the new string to the luhmann-tag zettel.getChild(ELEMENT_TRAILS).setText(sb.toString()); // addvalue was successfully added setModified(true); // return success return true; } // return success return false; } /** * Removes one or more manual links from the current entry... * * @param manlinks an integer-array with the manual links that should be removed... */ public void deleteManualLinks(String[] manlinks) { // get the current manual links... int[] current_mls = getCurrentManualLinks(); // if no manual links available, leave... if ((null == current_mls) || (current_mls.length < 1)) return; // if no manual links from parameter available, leave... if ((null == manlinks) || (manlinks.length < 1)) return; // create linked list and copy all current manual links to that list LinkedList<String> l = new LinkedList<String>(); for (int ml : current_mls) l.add(String.valueOf(ml)); // go through all entries that should be removed from the manual links... for (String mlparam : manlinks) { // find the entry in the linked list int pos = l.indexOf(mlparam); // if it exists, remove it. if (pos != -1) { l.remove(pos); // we also have to remove the *current* entry from the referred entry try { int mlparamentry = Integer.parseInt(mlparam); // therefore, get the manual links from the referred entry "mlparam" String[] backlinks = getManualLinksAsString(mlparamentry); // create new stringbuilder StringBuilder sb = new StringBuilder(""); // get current entry position as string. we need to remove this value // from the referred entry's manual links, given in the array "backlinks" String curentry = String.valueOf(zettelPos); // go through all manual links of the referred entry for (String bl : backlinks) { // if the manual link of the referred entry is *not* the current entry... if (!bl.equals(curentry)) { // append it to the string builder sb.append(bl); sb.append(","); } } // delete last comma if (sb.length() > 1) sb.setLength(sb.length() - 1); // and update manual links of the referred entry setManualLinks(mlparamentry, sb.toString()); } catch (NumberFormatException e) { Constants.zknlogger.log(Level.WARNING, e.getLocalizedMessage()); } } } // now we have all remaining manual links in the linked list "l". we now copy // each element of that list to a string-builder and set that string as new ELEMENT_MANLINKS // value for the current entry... StringBuilder sb = new StringBuilder(""); // create an iterator Iterator<String> i = l.iterator(); // go through list while (i.hasNext()) { // add each element to stringbuilder sb.append(i.next()); sb.append(","); } // truncate last comma if (sb.length() > 1) sb.setLength((sb.length() - 1)); // update manual links of current entry setManualLinks(zettelPos, sb.toString()); } /** * This method returns the content of an entry's luhmann-tag, i.e. the follower- * or sub-entries of an entry. These numbers are displayed in the tabbedpane in the * jTreeLuhmann (see ZettelkastenView.java for more details). * * @param pos the position of the entry which luhmann-numbers we want to have * @return a string with the comma-separated luhmann-numbers, or an empty string if the entry * has not luhmann-numbers. */ public String getLuhmannNumbers(int pos) { // get the entry Element zettel = retrieveElement(zknFile, pos); // if it exists... // return the content of the luhmann-child-element if (zettel != null && zettel.getChild(ELEMENT_TRAILS) != null) return zettel.getChild(ELEMENT_TRAILS).getText(); // return result return ""; } /** * This method returns the luhmann-numbers (follower-links) for an entry as string-array. * * @param pos the position of the entry which luhmann-numbers we want to have * @return an string-array containing the follower-entry-numbers where the entry {@code pos} refers to, * or {@code null} if no such follower-entry-numbers exist... */ public String[] getLuhmannNumbersAsString(int pos) { // get manual links String luh = getLuhmannNumbers(pos); // if no manual links there, quit... if (luh.isEmpty()) return null; // else split them into an array... String[] luhmann = luh.split(","); // if we have no manual links, return null... if ((null == luhmann) || luhmann.length < 1) return null; // return the content of the luhmann-child-element return luhmann; } /** * This method returns the luhmann-numbers (follower-links) for an entry as integer-array. * * @param pos the position of the entry which luhmann-numbers we want to have * @return an integer-array containing the follower-entry-numbers where the entry {@code pos} refers to, * or {@code null} if no such follower-entry-numbers exist... */ public int[] getLuhmannNumbersAsInteger(int pos) { // get manual links String luh = getLuhmannNumbers(pos); // if no manual links there, quit... if (luh.isEmpty()) return null; // else split them into an array... String[] luhmann = luh.split(","); // if we have no manual links, return null... if ((null == luhmann) || luhmann.length < 1) return null; // create integer array int[] luhint = new int[luhmann.length]; // copy string to int for (int cnt = 0; cnt < luhmann.length; cnt++) { try { luhint[cnt] = Integer.parseInt(luhmann[cnt]); } catch (NumberFormatException ex) { } } return luhint; } /** * This method returns the manual links for an entry as integer-array. * * @param pos the position of the entry which manual links we want to have * @return an integer array containing the entry-numbers where the current entry refers to, * or null if no entry-numbers exist... */ public int[] getManualLinks(int pos) { // get Manual Links as String Array String[] manlinks = getManualLinksAsString(pos); // if we have no manual links, return null... if ((null == manlinks) || manlinks.length < 1) return null; // create return value int[] retval = new int[manlinks.length]; // copy all string-numbers to int-array for (int cnt = 0; cnt < manlinks.length; cnt++) retval[cnt] = Integer.parseInt(manlinks[cnt]); // return the content of the luhmann-child-element return retval; } /** * Sets the manual links for an entry. these links appear in the main-window's tabbed pane * on the "links"-page, in the jTableManLinks. * * @param pos the entry-number that should get new manlinks * @param manlinks the entry-numbers where the entry "pos" refers to, stored in an integer-array */ public void setManualLinks(int pos, int[] manlinks) { // get the entry Element zettel = retrieveElement(zknFile, pos); // if we found an entry-element, go on if (zettel != null) { // if no child-element ELEMENT_MANLINKS exists, create it... if (null == zettel.getChild(ELEMENT_MANLINKS)) zettel.addContent(new Element(ELEMENT_MANLINKS)); // create stringbuilder StringBuilder sb = new StringBuilder(""); // iterate int-array for (int ml : manlinks) { // and copy all int-values to array sb.append(String.valueOf(ml)); sb.append(","); } // delete last comma if (sb.length() > 1) sb.setLength(sb.length() - 1); // and set value to element... zettel.getChild(ELEMENT_MANLINKS).setText(sb.toString()); setModified(true); } } /** * This method returns the manual links for an entry as string-array. * * @param pos the position of the entry which manual links we want to have * @return an string-array containing the entry-numbers where the entry {@code pos} refers to, * or {@code null} if no entry-numbers exist... */ public String[] getManualLinksAsString(int pos) { // get the entry Element zettel = retrieveElement(zknFile, pos); // if it exists... if (zettel != null && zettel.getChild(ELEMENT_MANLINKS) != null) { // get manual links String ml = zettel.getChild(ELEMENT_MANLINKS).getText(); // if no manual links there, quit... if (ml.isEmpty()) return null; // else split them into an array... String[] manlinks = ml.split(","); // if we have no manual links, return null... if ((null == manlinks) || manlinks.length < 1) return null; // return the content of the luhmann-child-element return manlinks; } // return result return null; } /** * This method returns the manual links for an entry as siingle string with * comma separated values * * @param pos the position of the entry which manual links we want to have * @return siingle string with comma separated values where the entry {@code pos} refers to, * or {@code null} if no entry-numbers exist... */ public String getManualLinksAsSingleString(int pos) { // get the entry Element zettel = retrieveElement(zknFile, pos); // if it exists... if (zettel != null && zettel.getChild(ELEMENT_MANLINKS) != null) { // get manual links String ml = zettel.getChild(ELEMENT_MANLINKS).getText(); // if no manual links there, quit... if (ml.isEmpty()) return null; // else split them into an array... // return the content of the luhmann-child-element return ml; } // return result return null; } /** * Sets the manual links for an entry. these links appear in the main-window's tabbed pane * on the "links"-page, in the jTableManLinks. * * @param pos the entry-number that should get new manlinks * @param manlinks the entry-numbers where the entry "pos" refers to, stored in a string-value * (comma-separated) */ public void setManualLinks(int pos, String manlinks) { // get the entry Element zettel = retrieveElement(zknFile, pos); // if we found an entry-element, go on if (zettel != null) { // if no child-element ELEMENT_MANLINKS exists, create it... if (null == zettel.getChild(ELEMENT_MANLINKS)) zettel.addContent(new Element(ELEMENT_MANLINKS)); // and set value to element... zettel.getChild(ELEMENT_MANLINKS).setText(manlinks); setModified(true); } } /** * This method returns the manual links for the current entry as integer-array. * * @return an integer array containing the entry-numbers where the current entry refers to, * or null if no entry-numbers exist... */ public int[] getCurrentManualLinks() { return getManualLinks(zettelPos); } /** * This method returns the manual links for the current entry as string-array. * * @return a string array containing the entry-numbers where the current entry refers to, * or null if no entry-numbers exist... */ public String[] getCurrentManualLinksAsString() { return getManualLinksAsString(zettelPos); } /** * This method checks, whether a given value already exist in an entry's luhmann-tag, or * in any of the entry's sub-entries luhmann-tags. We need this to prevent infinite loops * when displaying the sub-entries. An entry A may contain an entry B as sub-entry, but * entry B or any of entry B's sub-entries may not contain entry A! * * @param entry (the entry which luhmann-tag we want to check) * @param checkvalue (the entry which may not part of entry's luhmann-tag) * @param found (whether the checkvalue already exists in the enry's luhmann-tag or not, initially should be "false") * @return {@code true} when the checkvalue exists, false otherwise. actually the "found"-value is returned */ private boolean existsInLuhmann(int entry, int checkvalue, boolean found) { // if we found anything by now, return true if (found) return true; // get the entry Element zettel = retrieveElement(zknFile, entry); // if it exists, go on if (zettel != null && zettel.getChild(ELEMENT_TRAILS) != null) { // get the text from the luhmann-numbers String lnr = zettel.getChild(ELEMENT_TRAILS).getText(); // if we have any luhmann-numbers, go on... if (!lnr.isEmpty()) { // copy all values to an array String[] lnrs = lnr.split(","); // go throughh array of current luhmann-numbers for (String exist : lnrs) { // check whether luhmann-value exists, by re-calling this method // again and go through a recusrive loop found = existsInLuhmann(Integer.parseInt(exist), checkvalue, found); // if we have found a check-value, return true if (found) return true; // else check whether the current entry equals the checkvalue found = (entry == checkvalue); } } // else check whether the current entry equals the checkvalue else { found = (entry == checkvalue); } } // return result return found; } /** * This methods returns the author of a given position in the <b>author-datafile</b>.<br><br> * This method is used for creating the literatur list which is displayed in a * table on the JTabbedPane of the main window.<br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(AUCOUNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param pos a valid position of an element, ranged from 1 to {@link #getCount(int) getCount(AUCOUNT)} * @return the author string or an empty string if nothing was found */ public String getAuthor(int pos) { // retrieve the author element Element author = retrieveElement(authorFile, pos); // return the matching string value of the author element String retval; // check whether the element is null if (null == author) retval = ""; else retval = author.getText(); return retval; } /** * This method sets an author to a given position in the author datafile * could be used for overwriting/changing existing authors * * @param pos the position of the author. The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(AUCOUNT)} * @param auth the author string itself */ public void setAuthor(int pos, String auth) { setAuthorValue(pos, auth, null, -1); } /** * This method sets an author to a given position in the author datafile * could be used for overwriting/changing existing authors, including a * new bibkey value * * @param pos the position of the author. The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(AUCOUNT)} * @param auth the author string itself * @param bibkey optional, a bibkey reference for the author */ public void setAuthor(int pos, String auth, String bibkey) { setAuthorValue(pos, auth, bibkey, -1); } /** * This method sets an author to a given position in the author datafile * could be used for overwriting/changing existing authors * * @param pos the position of the author. The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(AUCOUNT)} * @param auth the author string itself * @param freq the frequency of the author. use <b>-1</b> when the frequency-attribute * should be left unchanged. */ public void setAuthor(int pos, String auth, int freq) { setAuthorValue(pos, auth, null, freq); } /** * This method sets an author to a given position in the author datafile * could be used for overwriting/changing existing authors, including a * new bibkey value * * @param pos the position of the author. The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(AUCOUNT)} * @param auth the author string itself * @param bibkey optional, a bibkey reference for the author * @param freq the frequency of the author. use <b>-1</b> when the frequency-attribute * should be left unchanged. */ public void setAuthor(int pos, String auth, String bibkey, int freq) { setAuthorValue(pos, auth, bibkey, freq); } /** * This method sets an author to a given position in the author datafile * could be used for overwriting/changing existing authors * * @param pos the position of the author. The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(AUCOUNT)} * @param auth the author string itself * @param freq the frequency of the author. use <b>-1</b> when the frequency-attribute * should be left unchanged. */ private void setAuthorValue(int pos, String auth, String bibkey, int freq) { // retrieve author Element author = retrieveElement(authorFile, pos); // if a valid element was found... if (author != null) { // ...set the new text author.setText(auth); // and new frequency, but only if it is not -1 if (freq != -1) author.setAttribute(ATTRIBUTE_FREQUENCIES, String.valueOf(freq)); // change bibkey if (bibkey != null) { setAuthorBibKey(auth, bibkey.trim()); } // and change the modified state of the file setModified(true); } } /** * This method deletes an author by removing the content from the element * inside of the author xml datafile. the element itself is kept and left * empty. this ensures that the order and numbering of an author never * changes. Since the zettelkasten datafile stores the index-numbers of the authors * a changing in the position/order/numbering of the author datafile would lead * to corrupted author associations in the zettelkasten data file * * @param pos position of author which should be deleted */ public void deleteAuthor(int pos) { // check whether author exists... if (!getAuthor(pos).isEmpty()) { // ...delete its content // therefore, get the author's index-number as string (for comparison below) String nr = String.valueOf(pos); // create new string buffer StringBuilder newau = new StringBuilder(""); // and delete this index-number from all entries for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // get each element Element zettel = retrieveElement(zknFile, cnt); // get the author-index-numbers String[] aunr = zettel.getChild(ELEMENT_AUTHOR).getText().split(","); // reset buffer newau.setLength(0); for (String aunr1 : aunr) { // if deleted value does not equal author-value, add it if (!aunr1.equals(nr)) { // append index-number newau.append(aunr1); // and a seperator-comma newau.append(","); } } // shorten the stringbuffer by one char, since we have a // superfluous comma char (see for-loop above) if (newau.length() > 0) newau.setLength(newau.length() - 1); // now set the new author-index-numbers to the zettel zettel.getChild(ELEMENT_AUTHOR).setText(newau.toString()); } // we don't want to remove the element itself, because this would lead // to changing index-numbers/element-position within the document. however, // an author should ever keep the same index-number. rather, we could fill // this "empty space" with new authors Element author = retrieveElement(authorFile, pos); // if we have an author, go on... if (author != null) { // clear text author.setText(""); // and reset attributes author.setAttribute(ATTRIBUTE_FREQUENCIES, "0"); author.setAttribute(ATTRIBUTE_AUTHOR_ID, ""); author.setAttribute(ATTRIBUTE_AUTHOR_TIMESTAMP, ""); // and reset bibkey author.setAttribute(ATTRIBUTE_AUTHOR_BIBKEY, ""); } // and change modified state setModified(true); } } /** * This function retrieves the title, content and author of an entry and * "converts" the data into a certain html layout which then appears in the * main window's textfield (jEditorPane). * <br><br> * <b>Caution!</b> Remember that {@code pos} has a range <i>from 1 to (size of zknfile)</i>, so we can directly * use the index number which are displayed in the jTable of the main window. However, * the access to the xml files are ranged between 0 and size-1, but this is achieved * in the retrieveElement-method, where we use "pos-1" to locate the correct entry * <br><br> * Use {@link #getZettelContent(int) getZettelContent(int)} if you need the plain entry * content as it is stored in the XML-file, without htnml-conversion. * @param pos the entry-number. use a number from 1 to {@link #getCount(int) getCount(ZKNCOUNT)} * @param segmentKeywords the keywords that are associated with certain segments or paragraphs of * that entry, so these paragraphs associated with an entry will be highlighted, when the entry * is selected in the main window. use {@code null} if not needed. * @param sourceframe a reference to the frame from where this function call came. needed for * the html-formatting, since entries are differently formatted in the search window. * @return a string array with the html layoutet content of the requested entry and author */ public String getEntryAsHtml(int pos, String[] segmentKeywords, int sourceframe) { // retrieve the entry Element entry = retrieveElement(zknFile, pos); // if no element exists, return empty array if (null == entry) return ""; // pass the title, content and author information to the html class // this class is responsible for doing the layout of the html page // which display an entry in the main window's JEditorPane // return the complete html page as string array, first element of the // array containing the main entry, second element the author information return HtmlUbbUtil.getEntryAsHTML(settings, this, bibtexObj, pos, segmentKeywords, sourceframe); } /** * This method retrieves the rating-attribute of entries and return the current rating * of entry {@code nr} as float-value.<br><br> * Rating can be a value from 0 to 5, including decimal place. * * @param nr the number of the entry which rating-value is requested. <b>Caution!</b> The parameter * {@code nr} has to be value from <i>1 to (size of {@link #getCount(int) getCount(ZKNCOUNT)})</i> - in contrary * to usual array handling where the range is from 0 to (size-1). * @return the rating of the entry as float-value, or {@code 0} (zero) if no rating exists. */ public float getZettelRating(int nr) { // check for valid parameter. If number-parameter is out of // bound, return 0 if (nr < 1 || nr > getCount(Daten.ZKNCOUNT)) return 0; // get entry Element entry = retrieveZettel(nr); // check for value return value if (entry != null) { // retrieve rating-attribute String rating = entry.getAttributeValue(ATTRIBUTE_RATING); // check whether rating-attribute exists if (rating != null && !rating.isEmpty()) { try { // try to convert value into float-variable and return that result float rateval = Float.parseFloat(rating); // and round to retrieve only one decimal place return (float) ((float) Math.round(rateval * 10) / 10.0); } catch (NumberFormatException ex) { // log error Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); Constants.zknlogger.log(Level.WARNING, "Could not parse rating value of entry {0}. Atribute-value is \"{1}\".", new Object[] { String.valueOf(nr), rating }); } } } // nothing found, return 0 return 0; } /** * * @param nr * @return */ public int getZettelRatingCount(int nr) { // check for valid parameter. If number-parameter is out of // bound, return 0 if (nr < 1 || nr > getCount(Daten.ZKNCOUNT)) return 0; // get entry Element entry = retrieveZettel(nr); // check for value return value if (entry != null) { // retrieve rating-attribute String ratingcount = entry.getAttributeValue(ATTRIBUTE_RATINGCOUNT); // check whether rating-attribute exists if (ratingcount != null && !ratingcount.isEmpty()) { try { // try to convert value into float-variable and return that result return Integer.parseInt(ratingcount); } catch (NumberFormatException ex) { // log error Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); Constants.zknlogger.log(Level.WARNING, "Could not parse rating-count value of entry {0}. Atribute-value is \"{1}\".", new Object[] { String.valueOf(nr), ratingcount }); } } } // nothing found, return 0 return 0; } /** * This method retrieves the rating-attribute of entries and return the current rating * of entry {@code nr} as string-value.<br><br> * Rating can be a value from 0 to 5, including decimal place. * * @param nr the number of the entry which rating-value is requested. <b>Caution!</b> The parameter * {@code nr} has to be value from <i>1 to (size of {@link #getCount(int) getCount(ZKNCOUNT)})</i> - in contrary * to usual array handling where the range is from 0 to (size-1). * @return the rating of the entry as string-value, or {@code "0"} if no rating exists. */ public String getZettelRatingAsString(int nr) { // get entry-rating as float-value and return it as string return String.valueOf(getZettelRating(nr)); } /** * * @param nr * @param rate * @return */ public boolean addZettelRating(int nr, float rate) { // check for valid parameter. If number-parameter is out of // bound, return 0 if (nr < 1 || nr > getCount(Daten.ZKNCOUNT)) return false; // get entry Element entry = retrieveZettel(nr); // check for value return value if (entry != null) { // check whether rating-attribute exists String rating = entry.getAttributeValue(ATTRIBUTE_RATING); // if attribute does not exist, create it if (null == rating) { // set rating-attribute entry.setAttribute(ATTRIBUTE_RATING, String.valueOf(rate)); // set rating count value to 1 entry.setAttribute(ATTRIBUTE_RATINGCOUNT, "1"); // set modified state setModified(true); // title list has to be updated setTitlelistUpToDate(false); // and quit return true; } else { // check whether rating-count value exists String ratingcount = entry.getAttributeValue(ATTRIBUTE_RATINGCOUNT); // if attribute does not exist, we have no base to calculate the average // rating, so we "reset" the rating by setting the new rating as default if (null == ratingcount) { // log error Constants.zknlogger.log(Level.WARNING, "Could not find rating-count attribute of entry {0}. The rating was reset to default value.", String.valueOf(nr)); // set rating-attribute entry.setAttribute(ATTRIBUTE_RATING, String.valueOf(rate)); // set rating count value to 1 entry.setAttribute(ATTRIBUTE_RATINGCOUNT, "1"); // set modified state setModified(true); // title list has to be updated setTitlelistUpToDate(false); // and quit return true; } // now calculate new average rating try { // try to convert value into float-variable and return that result float ratingvalue = Float.parseFloat(rating); // convert rating count int ratingcnt = Integer.parseInt(ratingcount); // check for valid values, i.e. if we have already rating-values, // and not for instance reset values. if (ratingcnt > 0 && ratingvalue > 0.0) { // calulate new rating float newrating = (float) (((ratingvalue * ratingcnt) + rate) / (ratingcnt + 1)); // set back new values entry.setAttribute(ATTRIBUTE_RATING, String.valueOf(newrating)); entry.setAttribute(ATTRIBUTE_RATINGCOUNT, String.valueOf(ratingcnt + 1)); } // in case we have reset-values, set new rate as default value else { // set rating-attribute entry.setAttribute(ATTRIBUTE_RATING, String.valueOf(rate)); // set rating count value to 1 entry.setAttribute(ATTRIBUTE_RATINGCOUNT, "1"); } // set modified state setModified(true); // title list has to be updated setTitlelistUpToDate(false); // return success return true; } catch (NumberFormatException ex) { // log error Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); Constants.zknlogger.log(Level.WARNING, "Could not parse rating value of entry {0}. Atribute-value is \"{1}\".", new Object[] { String.valueOf(nr), rating }); Constants.zknlogger.log(Level.WARNING, "Could not parse rating-count value of entry {0}. Atribute-value is \"{1}\".", new Object[] { String.valueOf(nr), ratingcount }); } } } return false; } /** * * @param nr */ public void resetZettelRating(int nr) { // check for valid parameter. If number-parameter is out of // bound, return 0 if (nr < 1 || nr > getCount(Daten.ZKNCOUNT)) return; // get entry Element entry = retrieveZettel(nr); // check for value return value if (entry != null) { // reset rating-values entry.setAttribute(ATTRIBUTE_RATING, "0"); entry.setAttribute(ATTRIBUTE_RATINGCOUNT, "0"); // set modified state setModified(true); // title list has to be updated setTitlelistUpToDate(false); } } /** * Checks whether an entry at the given {@code pos} is empty (thus deleted) or not. * @param pos the entry-number of that entry which has to be checked * @return {@code true} when the entry with the number {@code pos} is empty, false otherwise */ public boolean isEmpty(int pos) { // retrieve the entry Element entry = retrieveElement(zknFile, pos); // if no element exists, return false if (null == entry) return true; // else return whether content available or not return entry.getChild(ELEMENT_CONTENT).getText().isEmpty(); } /** * Checks whether an entry at the given {@code pos} is empty (thus deleted) or not. * @param entry * @return {@code true} when the entry with the number {@code pos} is empty, false otherwise */ public boolean isEmpty(Element entry) { // if no element exists, return false if (null == entry) return true; // else return whether content available or not return entry.getChild(ELEMENT_CONTENT).getText().isEmpty(); } /** * Checks whether an entry at the given {@code pos} is deleted or not. * @param pos the entry-number of that entry which has to be checked * @return {@code true} when the entry with the number {@code pos} is deleted, false otherwise */ public boolean isDeleted(int pos) { return isEmpty(pos); } /** * Checks whether an entry at the given {@code pos} is deleted or not. * @param entry the entry-element of that entry which has to be checked * @return {@code true} when the entry with the number {@code pos} is deleted, false otherwise */ public boolean isDeleted(Element entry) { return isEmpty(entry); } /** * This method checks an XML-database for existing entries. * @return {@code true} if the XML-file contains valid entries, * {@code false} if the XML-file contains no or only deleted entries. */ public boolean hasEntriesExcludingDeleted() { if (getCount(ZKNCOUNT) < 1) return false; for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { if (!isDeleted(cnt)) return true; } return false; } /** * This method returns the timestamp of the entry "pos". the timestamp is returned in a string-array. * the first part contains the created date, the second patr the edited date * @param pos the entry-number which timestamp we want to have * @return a stringarray, the first part holding the created date, the second part the edited date - or * {@code null} if no timestamp available. */ public String[] getTimestamp(int pos) { return getTimestamp(retrieveZettel(pos)); } /** * This method returns the timestamp of the entry "entry". the timestamp is returned in a string-array. * the first part contains the created date, the second patr the edited date * @param entry the entry-element which timestamp we want to have * @return a stringarray, the first part holding the created date, the second part the edited date - or * {@code null} if no timestamp available. */ public String[] getTimestamp(Element entry) { // if no element exists, return empty array if (null == entry) return null; // get created date String created = entry.getAttributeValue(ATTRIBUTE_TIMESTAMP_CREATED); // get edited date String edited = entry.getAttributeValue(ATTRIBUTE_TIMESTAMP_EDITED); return new String[] { created, edited }; } /** * This method returns the last modification (edited) timestamp of the entry "entry". * * @param entry the entry-element which edit-timestamp (last modification) we want to have * @return a string containig the edited date - or {@code null} if no timestamp available. */ public String getTimestampEdited(Element entry) { // if no element exists, return empty array if (null == entry) return null; // return edited date return entry.getAttributeValue(ATTRIBUTE_TIMESTAMP_EDITED); } /** * This method returns the last modification (edited) timestamp of the entry "entry". * * @param nr the entry-number which edit-timestamp (last modification) we want to have * @return a string containig the edited date - or {@code null} if no timestamp available. */ public String getTimestampEdited(int nr) { return getTimestampEdited(retrieveZettel(nr)); } /** * This method returns the creation timestamp of the entry "entry". * * @param entry the entry-element which created-timestamp we want to have * @return a string containig the creation date of that entry - or {@code null} if no timestamp available. */ public String getTimestampCreated(Element entry) { // if no element exists, return empty array if (null == entry) return null; // return edited date return entry.getAttributeValue(ATTRIBUTE_TIMESTAMP_CREATED); } /** * This method returns the last modification (edited) timestamp of the entry "entry". * * @param nr the entry-number which created-timestamp we want to have * @return a string containig the creation date of that entry - or {@code null} if no timestamp available. */ public String getTimestampCreated(int nr) { return getTimestampCreated(retrieveZettel(nr)); } /** * This method returns the links of an entry. since we can have more than just one * link/hyperlink per entry, the return-value is of the type {@code List<Element>}, i.e. * we return a list of xml-elements which contain the links of an entry. * * @param pos the entry from which we want to retrieve the hyperlinks * @return a List of xml-Elements, or null if no links are available */ public List<Element> getAttachments(int pos) { // retrieve the entry Element entry = retrieveElement(zknFile, pos); // if no element exists, return empty array if (null == entry) return null; // retrieve list of attachments List<Element> dummy = entry.getChild(ELEMENT_ATTACHMENTS).getChildren(); List<Element> attachments = new LinkedList<Element>(); // we have to manually copy all elements from one list to the other, // so we don't change the original content. Iterator<Element> it = dummy.iterator(); // go through list while (it.hasNext()) { // retrieve element Element att = it.next(); // change separator chars String attstring = Tools.convertSeparatorChars(att.getText(), settings); // add element if (!attstring.isEmpty()) { // create new element Element e = new Element(ELEMENT_ATTCHILD); // set text e.setText(attstring); // add element to return-list attachments.add(e); } } // else return the child-elements of the links-element return attachments; } /** * * @param pos * @return */ public boolean hasAttachments(int pos) { // retrieve the entry Element entry = retrieveElement(zknFile, pos); // if no element exists, return empty array if (null == entry) return false; // retrieve list of attachments List<Element> dummy = entry.getChild(ELEMENT_ATTACHMENTS).getChildren(); return (dummy != null && dummy.size() > 0); } /** * * @param pos * @return */ public boolean hasAuthors(int pos) { String[] aus = getAuthors(pos); return (aus != null && aus.length > 0); } /** * * @param pos * @return */ public boolean hasKeywords(int pos) { String[] kws = getKeywords(pos); return (kws != null && kws.length > 0); } /** * * @param pos * @return */ public boolean hasRemarks(int pos) { String rem = getRemarks(pos); return (!rem.isEmpty()); } /** * This method returns the links of an entry. since we can have more than just one * link/hyperlink per entry, the return-value is string-arra y which contain the * links of an entry. * * @param pos the entry from which we want to retrieve the hyperlinks * @param makeLinkToAttachment {@code true} if the attachment should be linked, in case the attachment * isan existing file on the hard disk. in this case, the attachment is surrounded by "file://" references. * @return a string-array of links/attachments, or null if no links are available */ public String[] getAttachmentsAsString(int pos, boolean makeLinkToAttachment) { // retrieve the entry Element entry = retrieveElement(zknFile, pos); // if no element exists, return empty array if (null == entry) return null; // get the child-elements of the links-element List<Element> links = entry.getChild(ELEMENT_ATTACHMENTS).getChildren(); // create iterator and copy all elements to a linked list Iterator<Element> i = links.iterator(); ArrayList<String> list = new ArrayList<String>(); // copy list to array while (i.hasNext()) { // get each link-element Element e = i.next(); // get link String link = e.getText(); if (!link.isEmpty()) { // convert separator chars link = Tools.convertSeparatorChars(link, settings); // if the attachment should be linked, check whether it is an existing file // if (makeLinkToAttachment) { // TODO hier noch weitermachen? Anhnge automatsch verlinken // if (!CCommonMethods.isHyperlink(link)) { // File linkfile = CCommonMethods.getLinkFile(settings, link); // // convert all file-attachments to hyperlinks // String file = "file://"; // if (System.getProperty("os.name").toLowerCase().startsWith("windows")) file = File.separatorChar+file; // link = "<a href=\""+file+linkfile.toString()+"\">"+link+"</a>"; // } // } list.add(link); } } return list.toArray(new String[list.size()]); } /** * This method sets the links of an entry. * * @param pos the entry from which we want to set/change the hyperlinks and attachments * @param attachments a string-array containing the hyperlinks, attachmentspaths etc. */ public void setAttachments(int pos, String[] attachments) { // retrieve the entry Element entry = retrieveElement(zknFile, pos); // if no element exists, return empty array if (null == entry || null == attachments || attachments.length < 1) return; // remove all existing links from that entry entry.getChild(ELEMENT_ATTACHMENTS).removeChildren(ELEMENT_ATTCHILD); // save modification-stata boolean mod = false; // add each hyperlink string // therefor, iterate the array for (String a : attachments) { try { // create a new subchuld-element Element sublink = new Element(ELEMENT_ATTCHILD); // add the link-string from the array sublink.setText(a); // and add sublink-element to entry's child's content entry.getChild(ELEMENT_ATTACHMENTS).addContent(sublink); // change modification state mod = true; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } } // change modified state if (mod) setModified(true); } /** * This method add the links to an entry. * * @param pos the entry from which we want to set/change the hyperlinks and attachments * @param attachments a string-array containing the hyperlinks, attachmentspaths etc. */ public void addAttachments(int pos, String[] attachments) { // retrieve the entry Element entry = retrieveElement(zknFile, pos); // if no element exists, return empty array if (null == entry || null == attachments || attachments.length < 1) return; // save modification-stata boolean mod = false; // add each hyperlink string // therefor, iterate the array for (String a : attachments) { try { // create a new subchuld-element Element sublink = new Element(ELEMENT_ATTCHILD); // add the link-string from the array sublink.setText(a); // and add sublink-element to entry's child's content entry.getChild(ELEMENT_ATTACHMENTS).addContent(sublink); // change modification state mod = true; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } catch (IllegalAddException ex) { Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); } } // change modified state if (mod) setModified(true); } /** * This method changes attachment-values of an entry. * * @param oldAttachment the old attachment-value of the entry {@code entrynr} before it was changed * @param newAttachment the new value that should replace {@code oldAttachment} * @param entrynr the number of the entry which attachment should be changed */ public void changeAttachment(String oldAttachment, String newAttachment, int entrynr) { // get links of entry List<Element> oldlinks = getAttachments(entrynr); // if we have any, we can go on... if (oldlinks != null && oldAttachment != null && newAttachment != null) { // create linked list that will contain the updated attachments List<String> attachments = new ArrayList<String>(); // iterator for current attachments of the entry Iterator<Element> i = oldlinks.iterator(); // go... while (i.hasNext()) { // retrieve each attachment as element Element e = i.next(); // get attachment-value String currentattachment = e.getText(); // if attachment-value equals the old value, replace it with the new value if (currentattachment.equals(oldAttachment)) currentattachment = newAttachment; // add attachment to linked list attachments.add(currentattachment); } // set links back to the entry setAttachments(entrynr, attachments.toArray(new String[attachments.size()])); } } /** * This method deletes an attachment-value of an entry. * * @param value the attachment-value that should be removed from the entry {@code entrynr} * @param entrynr the number of the entry which attachment should be changed */ public void deleteAttachment(String value, int entrynr) { // get links of entry List<Element> oldlinks = getAttachments(entrynr); // if we have any, we can go on... if (oldlinks != null) { // create linked list that will contain the updated attachments List<String> attachments = new ArrayList<String>(); // iterator for current attachments of the entry Iterator<Element> i = oldlinks.iterator(); // go... while (i.hasNext()) { // retrieve each attachment as element Element e = i.next(); // get attachment-value String currentattachment = e.getText(); // if attachment-value does not equals the delete-value, add attachment-value to list if (!currentattachment.equals(value)) attachments.add(currentattachment); } // set links back to the entry setAttachments(entrynr, attachments.toArray(new String[attachments.size()])); // change up-to-date-value setAttachmentlistUpToDate(false); } } /** * This method returns the remarks of a given entry. The entry-number which * remarks should be retrieved, is passed via paramter (pos). * * @param pos (the entry from which we want to retrieve the hyperlinks) * @return a string containing the remarks of an entry, or an empty string if no remarks found */ public String getRemarks(int pos) { // retrieve the element from the main xml-file Element el = retrieveElement(zknFile, pos); // if element or child element is null, return empty string if (null == el || null == el.getChild(ELEMENT_REMARKS)) return ""; // else return remarks return el.getChild(ELEMENT_REMARKS).getText(); } /** * This method changes the remarks of a given entry with the entry-number {@code pos}. * * @param pos the entry from which we want to change the remarks * @param remarks the new remarks-content * @return {@code true} if remarks have been successfully changed, false otherwise */ public boolean setRemarks(int pos, String remarks) { // retrieve the element from the main xml-file Element el = retrieveElement(zknFile, pos); // if element or child element is null, return false if (null == el || null == el.getChild(ELEMENT_REMARKS)) return false; try { // else change remarks el.getChild(ELEMENT_REMARKS).setText(remarks); // change modified state setModified(true); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } // and tell about success. return true; } /** * This method returns the cleaned remarks of a given entry, i.e. [br]-tags converted to * new lines. The entry-number which * remarks should be retrieved, is passed via paramter {@code pos}. * * @param pos the entry from which we want to retrieve the remarks * @return a string containing the cleaned remarks of an entry (i.e. [br]-tags converted to new * lines), or an empty string if no remarks found */ public String getCleanRemarks(int pos) { String content = getRemarks(pos); if (!content.isEmpty()) { content = content.replace("[br]", System.getProperty("line.separator")); } // return the cleaned string return content; } /** * This method retrieves all keywords of the currently <i>activated</i> entry * * @return a string array with all keywords of the current <i>activated</i> entry */ public String[] getCurrentKeywords() { return getKeywords(zettelPos); } /** * This method retrieves all keywords of a given entry. * * <b>Caution!</b> The position {@code pos} is a value <i>from 1 to (size of zknfile)</i> - in contrary * to usual array handling where the range is from 0 to (size-1) - , so we can directly * use the index number which are displayed in the jTable of the main window. However, * the access to the xml files are ranged between 0 and size-1, but this is achieved * in the retrieveElement-method, where we use "pos-1" to locate the correct entry * * @param pos a value from 1 to (size of zknfile), indicating the entry-number of which keywords are requested * @return a string array with all keywords of the requested entry, or <i>null</i> if no * keywords were found. */ public String[] getKeywords(int pos) { // first retrieve the current "zettel" element Element kw = retrieveElement(zknFile, pos); // if no element exist, return failed value if (null == kw) return null; // if no keyword index numbers exist, return failed value if (kw.getChild(ELEMENT_KEYWORD).getText().isEmpty()) return null; // then get the keyword indexnumbers String[] kwa = kw.getChild(ELEMENT_KEYWORD).getText().split(","); // create a new string array return value, which will contain the keyword strings String[] retval = new String[kwa.length]; // iterate the array // convert each keyword index number into an integer value // and get the related keyword string from the keyword data file // (this is achieved by the getKeyword-Method) for (int cnt = 0; cnt < kwa.length; cnt++) { retval[cnt] = getKeyword(Integer.parseInt(kwa[cnt])); } return retval; } /** * This method retrieves all keywords of a given entry. * * <b>Caution!</b> The position {@code pos} is a value <i>from 1 to (size of zknfile)</i> - in contrary * to usual array handling where the range is from 0 to (size-1) - , so we can directly * use the index number which are displayed in the jTable of the main window. However, * the access to the xml files are ranged between 0 and size-1, but this is achieved * in the retrieveElement-method, where we use "pos-1" to locate the correct entry * * @param pos a value from 1 to (size of zknfile), indicating the entry-number of which keywords are requested * @param sort {@code true} if keywords should be sorted alphabetically, {@code false} otherwise * @return a string array with all keywords of the requested entry, or <i>null</i> if no * keywords were found. */ public String[] getKeywords(int pos, boolean sort) { // retrieve entry's keywords String[] kws = getKeywords(pos); // if we have any, sort them if (kws != null && kws.length > 0) { // sort array if requested Arrays.sort(kws, new Comparer()); return kws; } // else return null else return null; } /** * This method retrieves all keywords of a given entry, but separates single keywords that consists of * several words. For instance, comma or divis-separated phrases in one keyword, would be split into single * parts. i.e. <b>Zettelkasten, programme</b> would be split into two return values: <b>Zettelkasten</b> and * <b>programme</b>. This method migh be useful for highlighting keywords... * * <b>Caution!</b> The position {@code pos} is a value <i>from 1 to (size of zknfile)</i> - in contrary * to usual array handling where the range is from 0 to (size-1) - , so we can directly * use the index number which are displayed in the jTable of the main window. However, * the access to the xml files are ranged between 0 and size-1, but this is achieved * in the retrieveElement-method, where we use "pos-1" to locate the correct entry * * @param pos a value from 1 to (size of zknfile), indicating the entry-number of which keywords are requested * @return a string array with all keywords of the requested entry, or <i>null</i> if no * keywords were found. */ public String[] getSeparatedKeywords(int pos) { // return separated keywords... return Tools.retrieveSeparatedKeywords(getKeywords(pos), false); } /** * This method retrieves all authors of a given entry. * * <b>Caution!</b> The position {@code pos} is a value <i>from 1 to (size of zknfile)</i> - in contrary * to usual array handling where the range is from 0 to (size-1) - so we can directly * use the index number which are displayed in the jTable of the main window. However, * the access to the xml files are ranged between 0 and size-1, but this is achieved * in the retrieveElement-method, where we use "pos-1" to locate the correct entry * * @param pos a value from 1 to (size of zknfile), indicating the entrynumber of the entry which authors are requested * @return a string array with all authors of the requested entry, or <i>null</i> if no author was found */ public String[] getAuthors(int pos) { // first retrieve the current "zettel" element Element au = retrieveElement(zknFile, pos); // if no element exist, return failed value if (null == au) return null; // if no author index numbers exist, return failed value if (au.getChild(ELEMENT_AUTHOR).getText().isEmpty()) return null; // then get the author indexnumbers String[] aunr = au.getChild(ELEMENT_AUTHOR).getText().split(","); // create a new string array return value, which will contain the author strings String[] retval = new String[aunr.length]; // iterate the array // convert each authorindex number into an integer value // and get the related keyword string from the author data file // (this is achieved by the getAuthor-Method) for (int cnt = 0; cnt < aunr.length; cnt++) retval[cnt] = getAuthor(Integer.parseInt(aunr[cnt])); return retval; } /** * This method returns the size of one of the xml data files. Following constants should * be used as parameters:<br> * ZKNCOUNT<br> * KWCOUNT<br> * AUCOUNT<br> * * @param what (uses constants, see global field definition at top of source) * @return the size of the requested data file */ public int getCount(int what) { Document doc; // check which file to count switch (what) { case ZKNCOUNT: doc = zknFile; break; case KWCOUNT: doc = keywordFile; break; case AUCOUNT: doc = authorFile; break; default: doc = zknFile; break; } // return XML file size return doc.getRootElement().getContentSize(); } /** * This method adds the new zettel-position to the history, so the user * can go back and fore to previous selected entries. */ private void addToHistory() { addToHistory(zettelPos); } /** * This method adds the entry-number {@code entrynr} to the history, so the user * can go back and fore to previous selected entries. * * @param entrynr the number of the entry that should be added to the history */ public void addToHistory(int entrynr) { // when the last history-entry equals the current entry, don't add // that to the history, so we don't have the same entry several times if (history[historyPosition] == entrynr) return; // when we reached the end of the array, rotate it... if (historyPosition >= (HISTORY_MAX - 1)) { // go through history array... // copy the next element the previous position for (int cnt = 0; cnt < (HISTORY_MAX - 1); cnt++) history[cnt] = history[cnt + 1]; // add new value to history history[HISTORY_MAX - 1] = entrynr; // set position and counter historyCount = HISTORY_MAX; historyPosition = HISTORY_MAX - 1; } else { // in any other case, simply increase the history counter historyPosition++; // add the new value history[historyPosition] = entrynr; // and set the internal counter. historyCount = historyPosition + 1; } } /** * Indicates whether the history-back function is possible or not. * @return {@code true}, if the histor-back-function is enabled, false otherwise */ public boolean canHistoryBack() { return (historyPosition > 0); } /** * Indicates whether the history-fore function is possible or not. * @return {@code true}, if the histor-fore-function is enabled, false otherwise */ public boolean canHistoryFore() { return (historyPosition < (historyCount - 1)); } /** * This methods goes back through the history and sets the current entry * to the related entry in the history... */ public void historyBack() { // check whether we can go back through history if (historyPosition > 0) { // if yes, decrease history position counter historyPosition--; // and set new zettel-position zettelPos = history[historyPosition]; } } /** * This methods goes fore through the history and sets the current entry * to the related entry in the history... */ public void historyFore() { // check whether we can go fore through history if (historyPosition < (historyCount - 1)) { // if yes, increase history position counter historyPosition++; // and set new zettel-position zettelPos = history[historyPosition]; } } /** * This method sets the index for the currently displayed entry * to a given number. With this method, we can directly go to * a certain entry. The entry itself is displayed via the "updateDisplay" method * from the main frame * * @param nr the number of the entry which should be displayed * @return */ public boolean gotoEntry(int nr) { // check whether it's out of bounds // and leave method if it is... if (!zettelExists(zettelPos) || isDeleted(nr)) return false; // else set the counter for the currently displayed entry zettelPos = nr; // update History addToHistory(); // and give positive feedback return true; } /** * This methods increases the counter of the currently displayed entry */ public void nextEntry() { // increase counter for currently display entry zettelPos = getNextZettel(zettelPos); // check whether it's out of bounds if (!zettelExists(zettelPos)) zettelPos = getFirstZettel(); // update History addToHistory(); } /** * This methods decreases the counter of the currently displayed entry */ public void prevEntry() { // decrease counter for currently display entry zettelPos = getPrevZettel(zettelPos); // check whether it's out of bounds if (!zettelExists(zettelPos)) zettelPos = getLastZettel(); // update History addToHistory(); } /** * This methods sets the counter of the currently displayed entry to the first entry */ public void firstEntry() { // set counter for currently display entry to 1 zettelPos = getFirstZettel(); // update History addToHistory(); } /** * This methods sets the counter of the currently displayed entry to the last entry * in the data file */ public void lastEntry() { // set counter for currently display entry to last element zettelPos = getLastZettel(); // update History addToHistory(); } /** * This method returns the index numbers of an entry's keywords as an integer array * This method is used for creating the links (connection between entries based on * matching keywords), which are displayed in a table on the JTabbedPane of the main window * * @param pos (the entry's number) * @return an array of integer values (the keyword index numbers of the requested entry), or * null if no keywords exist... */ public int[] getKeywordIndexNumbers(int pos) { // first retrieve the current "zettel" element Element dummy = retrieveElement(zknFile, pos); // if no element found, return failed value if (null == dummy) return null; // if no keyword index numbers exist, return failed value if (dummy.getChild(ELEMENT_KEYWORD).getText().isEmpty()) return null; // then get the keyword indexnumbers String[] kwa = dummy.getChild(ELEMENT_KEYWORD).getText().split(","); // create a new string array return value, which will contain the keyword strings int[] retval = new int[kwa.length]; // iterate the array // convert each keyword index number into an integer value // and get the related keyword string from the keyword data file // (this is achieved by the getKeyword-Method) for (int cnt = 0; cnt < kwa.length; cnt++) retval[cnt] = Integer.parseInt(kwa[cnt]); return retval; } /** * This method sets the index numbers of an entry's keywords, * the entry's reference to keyword values will be set. * <br><br> * This method does not affect the keyword-xml-file. * * @param pos (the entry-number of the entry, which keywords should be changed) * @param kws (a string with the keyword-index-numbers, separated by commas) */ public void setKeywordIndexNumbers(int pos, String kws) { setIndexNumbers(ELEMENT_KEYWORD, pos, kws); } /** * This method sets the index numbers of an entry's keywords or authors, * the entry's reference to keyword or author values will be set. * <br><br> * This method does neither affect the keyword- nor the author-xml-file. * * @param attr the attribut of which element should be changed. use either * {@link #ELEMENT_AUTHOR} or {@link #ELEMENT_KEYWORD}. * @param pos the entry-number of the entry, which authors/keywords should be changed * @param values a string with the keyword/author-index-numbers, separated by commas */ private void setIndexNumbers(String attr, int pos, String values) { // first retrieve the current "zettel" element Element dummy = retrieveElement(zknFile, pos); // if no element found, return failed value if (null == dummy) return; // set new keyword-index-numbers dummy.getChild(attr).setText(values); // change modified state setModified(true); } /** * This method sets the index numbers of an entry's author, i.e. * the entry's reference to author values will be set. * <br><br> * This method does not affect the author-xml-file. * * @param pos the entry-number of the entry, which authors should be changed * @param aus a string with the author-index-numbers, separated by commas */ public void setAuthorIndexNumbers(int pos, String aus) { setIndexNumbers(ELEMENT_AUTHOR, pos, aus); } /** * This method returns the index-numbers of an entry's authors as an integer value * * @param pos (the entry's number) * @return an integer array (the index numbers of the requested entry's authors), or null * if no author index numbers exist */ public int[] getAuthorIndexNumbers(int pos) { // first retrieve the current "zettel" element Element dummy = retrieveElement(zknFile, pos); // if no element found, return failed value if (null == dummy) return null; // if no author index numbers exist, return failed value if (dummy.getChild(ELEMENT_AUTHOR).getText().isEmpty()) return null; // then get the autors indexnumbers String[] aun = dummy.getChild(ELEMENT_AUTHOR).getText().split(","); // create a new string array return value, which will contain the authors strings int[] retval = new int[aun.length]; // iterate the array // convert each author index number into an integer value for (int cnt = 0; cnt < aun.length; cnt++) retval[cnt] = Integer.parseInt(aun[cnt]); return retval; } /** * This method counts the frequency (amount of appearance) of a keyword in the * main data file. Therefor, pass the index number of the keyword which has to be found. * After that, we iterate the main zkndata file, retrieving the keyword index numbers of * each entry, and then look for the appearance of "pos" (the given index number). Each time * we find that index number in an entry, the counter for the total frequency is increased by one. * * @param pos (the index number of the keyword which you are looking for) * @return (the amount of appearance / frequency of this keyword in the main data file) */ public int getKeywordFrequencies(int pos) { int retval = 0; // go through all entrys of the main data file (zknfile) for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // get the keyword index numbers of each entry int[] kwn = getKeywordIndexNumbers(cnt); // check whether we have any keywords at all if ((kwn != null) && (kwn.length > 0)) { // iterate the keyword index numbers of each entry for (int val : kwn) { // if a keyword index number matches the requested keyword // increase the keyword counter if (val == pos) { retval++; break; } } } } return retval; } /** * This method counts the frequency (amount of appearance) of a keyword in the * main data file. Therefor, pass the index number of the keyword which has to be found. * After that, we iterate the main zkndata file, retrieving the keyword index numbers of * each entry, and then look for the appearance of "pos" (the given index number). Each time * we find that index number in an entry, the counter for the total frequency is increased by one. * * @param kw the keyword of which the frequency should be counted * @return the amount of appearance / frequency of this keyword in the main data file */ public int getKeywordFrequencies(String kw) { int retval = 0; // go through all entrys of the main data file (zknfile) for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // check whether we have any keywords at all if (existsInKeywords(kw, cnt, true)) retval++; } return retval; } /** * This method counts the frequency (amount of appearance) of a keyword in the * main data file. Therefor, pass the index number of the keyword which has to be found. * After that, we iterate the main zkndata file, retrieving the keyword index numbers of * each entry, and then look for the appearance of "pos" (the given index number). Each time * we find that index number in an entry, the counter for the total frequency is increased by one. * * @param pos (the index number of the keyword which you are looking for) * @return (the amount of appearance / frequency of this keyword in the main data file) */ public int getKeywordFrequency(int pos) { // retrieve the keyword element Element keyword = retrieveElement(keywordFile, pos); // check whether element is null if (null == keyword) { return 0; } else { try { String freq = keyword.getAttributeValue(ATTRIBUTE_FREQUENCIES); if (freq != null) { return Integer.parseInt(freq); } else { return 0; } } catch (NumberFormatException ex) { return 0; } } } /** * This method counts the frequency (amount of appearance) of an author in the * main data file. Therefor, pass the index number of the author which has to be found. * After that, we iterate the main zkndata file, retrieving the author index numbers of * each entry, and then look for the appearance of "pos" (the given index number). Each time * we find that index number in an entry, the counter for the total frequency is increased by one. * * @param pos the index number of the author which you are looking for * @return the amount of appearance / frequency of this author in the main data file */ public int getAuthorFrequencies(int pos) { int retval = 0; // go through all entrys of the main data file (zknfile) for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // get the author index numbers of each entry int[] aun = getAuthorIndexNumbers(cnt); // check whether we have any author index numbers at all if ((aun != null) && (aun.length > 0)) { // iterate the author index numbers of each entry for (int val : aun) { // if an author index number matches the requested author // increase the keyword counter if (val == pos) { retval++; break; } } } } return retval; } /** * This method counts the frequency (amount of appearance) of an author in the * main data file. Therefor, pass the index number of the author which has to be found. * After that, we iterate the main zkndata file, retrieving the author index numbers of * each entry, and then look for the appearance of "pos" (the given index number). Each time * we find that index number in an entry, the counter for the total frequency is increased by one. * * @param au the author-value of which the frequency should be counted * @return the amount of appearance / frequency of this author in the main data file */ public int getAuthorFrequencies(String au) { int retval = 0; // go through all entrys of the main data file (zknfile) for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // check whether we have any keywords at all if (existsInAuthors(au, cnt)) retval++; } return retval; } /** * This method counts the frequency (amount of appearance) of a keyword in the * main data file. Therefor, pass the index number of the keyword which has to be found. * After that, we iterate the main zkndata file, retrieving the keyword index numbers of * each entry, and then look for the appearance of "pos" (the given index number). Each time * we find that index number in an entry, the counter for the total frequency is increased by one. * * @param pos the index number of the author which you are looking for * @return the amount of appearance / frequency of this author in the main data file */ public int getAuthorFrequency(int pos) { // retrieve the keyword element Element author = retrieveElement(authorFile, pos); // check whether element is null if (null == author) return 0; else return Integer.parseInt(author.getAttributeValue(ATTRIBUTE_FREQUENCIES)); } /** * This method returns the bibkey-string of an author-value. the bibkey-string referres to a * BibTex-entry in a given BibTex-file, so the "formatted" author of the author-value saved * in our authorXml-file can be retrieved via a BibTex-File.<br><br> * This attribute is optional, so {@code null} might be returned. * * @param pos the index number of the author which you are looking for. Remember that this value * has a range from 1 to {@link #getCount(int) getCount(AUCOUNT)}. * @return a string containing the {@code bibkey} string of the author. if no such attribute or no * such author-element exists, {@code null} is returned. if attribute is empty, an empty string * is returned. */ public String getAuthorBibKey(int pos) { return getAuthorBibKeyValue(pos); } /** * This method returns the bibkey-string of an author-value. the bibkey-string referres to a * BibTex-entry in a given BibTex-file, so the "formatted" author of the author-value saved * in our authorXml-file can be retrieved via a BibTex-File.<br><br> * This attribute is optional, so {@code null} might be returned. * * @param au the author-value as string * @return a string containing the {@code bibkey} string of the author. if no such attribute or no * such author-element exists, {@code null} is returned. if attribute is empty, an empty string * is returned. */ public String getAuthorBibKey(String au) { return getAuthorBibKeyValue(getAuthorPosition(au)); } /** * This method returns the bibkey-string of an author-value. the bibkey-string referres to a * BibTex-entry in a given BibTex-file, so the "formatted" author of the author-value saved * in our authorXml-file can be retrieved via a BibTex-File.<br><br> * This attribute is optional, so {@code null} might be returned.<br><br> * This method does the work for both * {@link #getAuthorBibKey(java.lang.String) getAuthorBibKey(String)} and * {@link #getAuthorBibKey(int) getAuthorBibKey(int)}. * * @param pos the index number of the author which you are looking for. Remember that this value * has a range from 1 to {@link #getCount(int) getCount(AUCOUNT)}. * @return a string containing the {@code bibkey} string of the author. if no such attribute or no * such author-element exists, {@code null} is returned. if attribute is empty, an empty string * is returned. */ private String getAuthorBibKeyValue(int pos) { // if we have no such author, return null if (-1 == pos || pos > getCount(AUCOUNT)) return null; // retrieve the keyword element Element author = retrieveElement(authorFile, pos); // check whether element is null if (null == author) return null; // else return the attribute-value return author.getAttributeValue(ATTRIBUTE_AUTHOR_BIBKEY); } /** * This method sets the bibkey-string of an author-value. the bibkey-string referres to a * BibTex-entry in a given BibTex-file, so the "formatted" author of the author-value saved * in our authorXml-file can be retrieved via a BibTex-File. * * @param pos the index number of the author which you are looking for * @param key the bibkey of the related BibTex-entry. * @return {@code true} if bibkey-attribute was successfully changed, {@code false} if an error occured */ public boolean setAuthorBibKey(int pos, String key) { return setAuthorBibKeyValue(pos, key.trim()); } /** * This method sets the bibkey-string of an author-value. the bibkey-string referres to a * BibTex-entry in a given BibTex-file, so the "formatted" author of the author-value saved * in our authorXml-file can be retrieved via a BibTex-File. * * @param au the author-value as string of that author where the bibkey-value should be changed * @param key the bibkey of the related BibTex-entry. * @return {@code true} if bibkey-attribute was successfully changed, {@code false} if an error occured */ public boolean setAuthorBibKey(String au, String key) { // check for valid values if (null == au || null == key || au.isEmpty()) { return false; } // retrieve author-position int pos = getAuthorPosition(au); // if we have no such author, return null if (-1 == pos) return false; return setAuthorBibKeyValue(pos, key.trim()); } /** * This method sets the bibkey-string of an author-value. the bibkey-string referres to a * BibTex-entry in a given BibTex-file, so the "formatted" author of the author-value saved * in our authorXml-file can be retrieved via a BibTex-File.<br><br> * This method does the work for both * {@link #setAuthorBibKey(java.lang.String, java.lang.String) setAuthorBibKey(String, String)} * and {@link #setAuthorBibKey(int, java.lang.String) setAuthorBibKey(int, String)}. * * @param pos the index number of the author which you are looking for * @param key the bibkey of the related BibTex-entry. * @return {@code true} if bibkey-attribute was successfully changed, {@code false} if an error occured */ private boolean setAuthorBibKeyValue(int pos, String key) { // retrieve the keyword element Element author = retrieveElement(authorFile, pos); // check whether element is null if (null == author) return false; try { // if everything ok, set new attribute-value author.setAttribute(ATTRIBUTE_AUTHOR_BIBKEY, key); // and change modified-state setModified(true); } catch (IllegalNameException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); return false; } return true; } /** * This method sets the current entry, that means the last activated entry before closing * the program. With this, we can show the last shown entry on startup of the program. * <br><br> * This method should be called directly before closing a data-file! * @param nr */ public void setCurrentZettelPos(int nr) { zettelPos = nr; } /** * This method sets the initial history value. This method should be used when * the startup-entry or a random-startup-entry is set. * @param nr */ public void setInitialHistoryPos(int nr) { history[0] = nr; } /** * This method returns the number of the currently <i>activated</i> entry * @return number of the currently <i>activated</i> entry */ public int getCurrentZettelPos() { // in case, the position-index for the displayed to be displayed is out // of the valid boundaries, simply reset the position-index to 1 if (!zettelExists(zettelPos)) zettelPos = getFirstZettel(); // and return the value return zettelPos; } /** * This method calcualtes the relevance or strength of the connection of two entries, given * by their keywords. the more keywords of the {@code sourceentry} also are keywords * of the {@code destentry}, the higher the strength of the connection is. The maximum value is * 100%, i.e. each keyword of {@code sourceentry} is also a keyword of {@code destentry}.<br><br> * The strength is returned as a ratio, saying how many percent of {@code sourceentry}'s keywords * are also keywords of {@code destentry}. * * @param sourceentry the source entry, usually that one that currently is being displayed, which * is used as base for calculating the connection-ration * @param destentry one of the entries that is connected with the {@code sourceentry} via * identical keywords. * @return the strength of the entry-connecion, an integer-ratio with a maximum of 100% (i.e. * the return-value is an integer-value ranged from 0 to 100), saying how many percent of * {@code sourceentry}'s keywords are also keywords of {@code destentry}. */ public int getLinkStrength(int sourceentry, int destentry) { // check for valid parameters if (sourceentry < 0 || sourceentry > getCount(ZKNCOUNT) || destentry < 0 || destentry > getCount(ZKNCOUNT)) return 0; // retrieve all keywords of source-entry String[] kws = getKeywords(sourceentry); // check for valid values if (null == kws || kws.length < 1) return 0; // init counter for amount of identical keywords int keycnt = 0; // go through all keywords of the source-entry and check for existence for (String k : kws) { // check for existens of each keyword, and increase counter if necessary if (existsInKeywords(k, destentry, false)) keycnt++; } // calculate ratio int keylen = kws.length; double ratio = (double) 100.0 * keycnt / keylen; // return result return (int) ratio; } /** * This method returns the title of a certain entry * * @param pos the index number of the entry which title is requested. Must be a number from 1 * to {@link #getCount(int) getCount(CDaten.ZKNCOUNT)}. * @return the title of the requested entry as a string, or an empty string if an error occured */ public String getZettelTitle(int pos) { // retrieve the element from the main xml-file Element el = retrieveElement(zknFile, pos); // if element or child element is null, return empty string if (null == el || null == el.getChild(ELEMENT_TITLE)) return ""; // else return title return el.getChild(ELEMENT_TITLE).getText(); } /** * This method returns the title of a certain entry * * @param pos the index number of the entry which title is requested. Must be a number from 1 * to {@link #getCount(int) getCount(CDaten.ZKNCOUNT)}. * @param title */ public void setZettelTitle(int pos, String title) { // retrieve the element from the main xml-file Element el = retrieveElement(zknFile, pos); // if element or child element is null, return empty string if (null == el || null == el.getChild(ELEMENT_TITLE)) return; try { // else change title el.getChild(ELEMENT_TITLE).setText(title); // reset title-list setTitlelistUpToDate(false); // change modified state setModified(true); } catch (IllegalDataException ex) { Constants.zknlogger.log(Level.SEVERE, ex.getLocalizedMessage()); } } /** * This method changes the "edited" timestamp of the entry with the index number * {@code pos}. By this, you can change the edited-date from an entry manually, if necessary. * The timestamp uses the current date, retrieved from {@code CCommonMethods.getTimestamp()} * * @param pos the entry's index-number of that entry which edited-timestamp should be changed. * {@code pos} has to be a value from 1 to {@link #getCount(int) getCount(CDaten.ZKNCOUNT)}. */ public void changeEditTimeStamp(int pos) { // retrieve the element from the main xml-file Element el = retrieveElement(zknFile, pos); // if element or child element is null, return empty string if (null == el) return; // set timestamp setTimestampEdited(el, Tools.getTimeStamp()); } /** * This method returns the content of a certain entry, i.e. * the main entry text (text excerpt or whatever). The content * is returned as it is stored in the XML-datafile. So we have * the "plain text" here, <i>with</i> format-tags, but <i>not</i> prepared * for HTML-display.<br><br> * Use {@link #getEntryAsHtml(int, java.lang.String[]) getEntryAsHtml()} * if you need the HTML-formatted entry instead.<br><br> * Use {@link #getCleanZettelContent(int) getCleanZettelContent()} if you need * the plain text entry <i>without</i> format-tags. * * @param pos the index number of the entry which content is requested. Must be a number from 1 * to {@link #getCount(int) getCount(CDaten.ZKNCOUNT)}. * @return the plain, non-html-converted content of the requested entry as a string * or an empty string if no entry was found or the requested entry does not exist */ public String getZettelContent(int pos) { // retrieve the element from the main xml-file Element el = retrieveElement(zknFile, pos); // if element or child element is null, return empty string if (null == el || null == el.getChild(ELEMENT_CONTENT)) return ""; // else return title return el.getChild(ELEMENT_CONTENT).getText(); } /** * This method returns the content of a certain entry, i.e. * the main entry text (text excerpt or whatever). The content * is returned in HTML-format.<br><br> * Use {@link #getCleanZettelContent(int) getCleanZettelContent()} if you need * the plain text entry <i>without</i> format-tags. * * @param pos the index number of the entry which content is requested. Must be a number from 1 * to {@link #getCount(int) getCount(CDaten.ZKNCOUNT)}. * @return the html-converted content of the requested entry as a string * or an empty string if no entry was found or the requested entry does not exist */ public String getZettelContentAsHtml(int pos) { // retrieve the element from the main xml-file Element el = retrieveElement(zknFile, pos); // if element or child element is null, return empty string if (null == el || null == el.getChild(ELEMENT_CONTENT)) return ""; // else return entry as html return HtmlUbbUtil.convertUbbToHtml(settings, this, bibtexObj, el.getChild(ELEMENT_CONTENT).getText(), Constants.FRAME_MAIN, false, false); } /** * This method returns the content of a certain entry, i.e. * the main entry text (text excerpt or whatever). The content * is returned as it is stored in the XML-datafile. So we have * the "plain text" here, <i>with</i> format-tags, but <i>not</i> prepared * for HTML-display.<br><br> * However, you can encode Unicode chars into its equivalent HTML entities * nby setting the parameter {@code encodeUTF} to {@code true}. This is * necessary when exporting entries to HTML or PDF. * <br><br> * Use {@link #getEntryAsHtml(int, java.lang.String[]) getEntryAsHtml()} * if you need the HTML-formatted entry instead.<br><br> * Use {@link #getCleanZettelContent(int) getCleanZettelContent()} if you need * the plain text entry <i>without</i> format-tags. * * @param pos the index number of the entry which content is requested. Must be a number from 1 * to {@link #getCount(int) getCount(CDaten.ZKNCOUNT)}. * @param encodeUTF if {@code true}, unicode characters are encoded to the equivalent * HTML entities. * @return the plain, non-html-converted content of the requested entry as a string * or an empty string if no entry was found or the requested entry does not exist */ public String getZettelContent(int pos, boolean encodeUTF) { // retrieve the element from the main xml-file Element el = retrieveElement(zknFile, pos); // if element or child element is null, return empty string if (null == el || null == el.getChild(ELEMENT_CONTENT)) return ""; // retrieve entry's content String preparestring = el.getChild(ELEMENT_CONTENT).getText(); // create dummy-string-builder StringBuilder buf = new StringBuilder(""); // iterate each char of the string 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 converted string return buf.toString(); } /** * This method sets the content of a certain entry, i.e. * the main entry text (text excerpt or whatever). * * @param pos the index number of the entry which content is requested. Must be a number from 1 * to {@link #getCount(int) getCount(CDaten.ZKNCOUNT)}. * @param content the new content of the entry * @param changetimestamp {@code true} if the entry's modified-timestamp should be updated * @return {@code true} if content was successfully changed, false otherwise */ public boolean setZettelContent(int pos, String content, boolean changetimestamp) { // retrieve the element from the main xml-file Element el = retrieveElement(zknFile, pos); // if element or child element is null, return empty string if (null == el || null == el.getChild(ELEMENT_CONTENT)) return false; // else set new content el.getChild(ELEMENT_CONTENT).setText(content); // and change timestamp if (changetimestamp) changeEditTimeStamp(pos); // change modified state setModified(true); // and tell about success... return true; } /** * This method returns the cleaned content of an entry. Usually, the getZettelContent()-method * contains also the formatting-tags (like [f] or [k], see CHtml-class for more details). Since * these tags may also appear inside a word or phrase, search-results may miss this "splitted" * word and don't recognize that entry as valied-search-hit. * <br><br> * Therefore, when we want to search through the content (see CStartSearch-class for more details) * we want to have a clean text, removing all formatting tags... * * @param pos (the entry-index-number) * @return the cleaned content of that entry, with all formatting-tags removed */ public String getCleanZettelContent(int pos) { // get the zettel content String content = getZettelContent(pos); // if the content is not empty... if (!content.isEmpty()) { // return the cleaned string return Tools.removeUbbFromString(content, true); } return ""; } /** * This method sets the keyword-up-to-date-state. This is used when creating * the keyword list via "CShowKeywordListDialog". This background task only needs * to be executed once and then only again after changes have been made to the keyword list. * Otherwise, switching tabs in the tabbedpane of the main window takes too long. * * @param val (the new state, whether the keywordlist is uptodate or not) */ public void setKeywordlistUpToDate(boolean val) { keywordlistUpToDate = val; } /** * This method gets the keyword-up-to-date-state. The background task for creating the * keyword list in the tabbedpane of the main window only needs * to be executed when changes have been made to the keyword list. * Otherwise, switching tabs in the tabbedpane of the main window takes too long. * * @return whether the keywordlist is uptodate or not */ public boolean isKeywordlistUpToDate() { return keywordlistUpToDate; } /** * This method sets the cluster-up-to-date-state. This is used when creating * the cluster list. This rebuilding only needs * to be executed once and then only again after changes have been made to the keyword list. * Otherwise, switching tabs in the tabbedpane of the main window takes too long. * * @param val the new state, whether the clusterlist is uptodate or not */ public void setClusterlistUpToDate(boolean val) { clusterlistUpToDate = val; } /** * This method gets the cluster-up-to-date-state. The rebuilding of the * cluster list in the tabbedpane of the main window only needs * to be executed when changes have been made to the keyword list. * Otherwise, switching tabs in the tabbedpane of the main window takes too long. * * @return whether the clusterlist is uptodate or not */ public boolean isClusterlistUpToDate() { return clusterlistUpToDate; } /** * This method sets the author-up-to-date-state. This is used when creating * the author list via "CShowAuthorListDialog". This background task only needs * to be executed once and then only again after changes have been made to the author list. * Otherwise, switching tabs in the tabbedpane of the main window takes too long. * * @param val the new state, whether the authorlist is uptodate or not */ public void setAuthorlistUpToDate(boolean val) { authorlistUpToDate = val; } /** * This method gets the author-up-to-date-state. The background task for creating the * author list in the tabbedpane of the main window only needs * to be executed when changes have been made to the author list. * Otherwise, switching tabs in the tabbedpane of the main window takes too long. * * @return whether the authorlist is uptodate or not */ public boolean isAuthorlistUpToDate() { return authorlistUpToDate; } /** * This method sets the title-up-to-date-state. This is used when creating * the title list via "CShowTitleListDialog". This background task only needs * to be executed once and then only again after changes have been made to the entries. * Otherwise, switching tabs in the tabbedpane of the main window takes too long. * * @param val the new state, whether the titlelist is uptodate or not */ public void setTitlelistUpToDate(boolean val) { titlelistUpToDate = val; } /** * This method gets the title-up-to-date-state. The background task for creating the * title list in the tabbedpane of the main window only needs * to be executed when changes have been made to the entries. * Otherwise, switching tabs in the tabbedpane of the main window takes too long. * * @return whether the titlelist is uptodate or not */ public boolean isTitlelistUpToDate() { return titlelistUpToDate; } /** * This method sets the attachment-up-to-date-state. This is used when creating * the attachment list via "CShowAttachmentListDialog". This background task only needs * to be executed once and then only again after changes have been made to the entries. * Otherwise, switching tabs in the tabbedpane of the main window takes too long. * * @param val the new state, whether the attachmentlist is uptodate or not */ public void setAttachmentlistUpToDate(boolean val) { attachmentlistUpToDate = val; } /** * This method gets the attachment-up-to-date-state. The background task for creating the * attachment list in the tabbedpane of the main window only needs * to be executed when changes have been made to the entries. * Otherwise, switching tabs in the tabbedpane of the main window takes too long. * * @return whether the attachmentlist is uptodate (true) or not (false) */ public boolean isAttachmentlistUpToDate() { return attachmentlistUpToDate; } /** * This method is used in the {@link de.danielluedecke.zettelkasten.tasks.export.ExportToZknTask} class * to prepare the entries that should be exported. This method converts entry-number-references * into the related entry-IDs using the {@link #getZettelID(int) getZettelID(int)} method. * * @param entrynumbers * @return */ public boolean createExportEntries(ArrayList<Integer> entrynumbers) { // check for valid parameter if (entrynumbers != null && entrynumbers.size() > 0) { // create "empty" XML JDom objects zknFileExport = new Document(new Element(DOCUMENT_ZETTELKASTEN)); for (Integer entrynumber : entrynumbers) { // create new zettel element // and clone content from requested zettel to our element Element zettel = (Element) retrieveZettel(entrynumber).clone(); // retrieve content of entry and convert all author footnotes, which // contain author-index-numbers, into the related author-IDs. String content = zettel.getChild(ELEMENT_CONTENT).getText(); // check for footnotes int pos = 0; while (pos != -1) { // find the html-tag for the footnote pos = content.indexOf(Constants.FORMAT_FOOTNOTE_OPEN, pos); // if we found something... if (pos != -1) { // find the closing quotes int end = content.indexOf("]", pos + 2); // if we found that as well... if (end != -1) { try { // extract footnote-number String fn = content.substring(pos + 4, end); // retrieve author ID from related footnote number try { String authorID = getAuthorID(Integer.parseInt(fn)); // replace author number with author ID inside footnote content = content.substring(0, pos + 4) + authorID + content.substring(end); } catch (NumberFormatException ex) { // log error Constants.zknlogger.log(Level.WARNING, ex.getLocalizedMessage()); Constants.zknlogger.log(Level.WARNING, "Could not convert author number into author ID!"); } } catch (IndexOutOfBoundsException ex) { } // and add it to the linked list, if it doesn't already exist // set pos to new position pos = end; } else pos = pos + 4; } } // check for manual links pos = 0; while (pos != -1) { // find the html-tag for the manual link pos = content.indexOf(Constants.FORMAT_MANLINK_OPEN, pos); // if we found something... if (pos != -1) { // find the closing quotes int end = content.indexOf("]", pos + 2); // if we found that as well... if (end != -1) { try { // extract manual-link-number String ml = content.substring(pos + 3, end); // retrieve author ID from related footnote number try { String zetID = getZettelID(Integer.parseInt(ml)); // replace manual link number with entry ID content = content.substring(0, pos + 3) + zetID + content.substring(end); } catch (NumberFormatException ex) { // log error Constants.zknlogger.log(Level.WARNING, "Could not convert manual link number into related entry ID!"); } } catch (IndexOutOfBoundsException ex) { } // and add it to the linked list, if it doesn't already exist // set pos to new position pos = end; } else pos = pos + 3; } } // set back changes zettel.getChild(ELEMENT_CONTENT).setText(content); // // here we change the entry's luhmann-numbers (trailing entries) and the // entry's manual links with the unique IDs // replaceAttributeNrWithID(zettel); // add each entry-element to the export-document zknFileExport.getRootElement().addContent(zettel); } // set first and last zettel zknFileExport.getRootElement().setAttribute(ATTRIBUTE_FIRST_ZETTEL, ""); zknFileExport.getRootElement().setAttribute(ATTRIBUTE_LAST_ZETTEL, ""); return true; } return false; } /** * This method returns the XML-document that has been prepared for exporting into the * Zettelkasten-fileformat. * * @return XML-document that has been prepared for export */ public Document retrieveExportDocument() { return zknFileExport; } /** * This method removes wrong placed edit-tags in the xml-file. this error occured in the * {@link #changeEditTimeStamp(int) changeEditTimeStamp()} method, where the edit-element, * child-element of the timestamp-element, was set as child of the zettel-element (and not its * child "timestamp"). This method tries to fix this error... */ public void fixWrongEditTags() { // iterate all elements for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // retrieve element Element zettel = retrieveZettel(cnt); // check for valid value if (zettel != null) { // check whether element has a child named "edited". if so, it either // has to be moved as sub-child to the child-element timestamp, or removed // if "timestamp" already has an edit-element Element edited = zettel.getChild("edited"); // only proceed, if wrong placed edited element exists if (edited != null) { // retrieve timestamp-element Element timestamp = zettel.getChild("timestamp"); // check for valid value if (timestamp != null) { // retrieve edited-timestamp Element timestampedit = timestamp.getChild("edited"); // check whether edited-element exists if (null == timestampedit) { // if timestampedit is null, the element has no edited-element // so we add the content of the wrong placed element as new edited-element // create new edited element Element ed = new Element("edited"); // add to timestamp timestamp.addContent(ed); // set content ed.setText(edited.getText()); } else { // now we know that an edited-element already exists // we now want to check whether the existing editing-timestamp // is older than the value in the wrong placed edited-element, // and if so, update the timestamp if (timestamp.getText().compareTo(edited.getText()) < 0) timestampedit.setText(edited.getText()); } } // and remove wrong edited element zettel.removeChild("edited"); } } } } /** * This methods updates the data base when updating to versiob 3.3. Each entry element * receives a unique ID as XML-attribute value stored in the data base. The unique ID consists * of entry's edited timestamp, file name and entry number. */ public void db_updateZettelIDs() { // iterate all elements for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // retrieve element Element zettel = retrieveZettel(cnt); // check for valid value // and check whether entry already has an ID if (zettel != null && !hasZettelID(cnt)) { // if not, set unique ID-attribute to entry // init variable StringBuilder id = new StringBuilder(""); // retrieve timestamp String[] ts = getTimestamp(cnt); // check for valid entry if (ts != null && ts[0] != null && !ts[0].isEmpty()) { // append timestamp id.append(ts[0]); } else { // else, if entry has no create-timestamp, add manual timestamp id.append(Tools.getTimeStampWithMilliseconds()); } id.append(String.valueOf(cnt)).append(settings.getFileName()).append(String.valueOf(cnt)); // now add id to zettel-element zettel.setAttribute(ATTRIBUTE_ZETTEL_ID, id.toString()); } } // change modified state setModified(true); } /** * This method updates the data base when updating from data base version 3.3 to 3.4. * Here each author and keyword element in the {@link #authorFile} and {@link #keywordFile} * get 2 new attributes: {@link #ATTRIBUTE_AUTHOR_TIMESTAMP} and {@link #ATTRIBUTE_AUTHOR_ID} (resp. * their keyword-equivalence). */ public void db_updateAuthorAndKeywordIDs() { // iterate all elements for (int cnt = 1; cnt <= getCount(AUCOUNT); cnt++) { // retrieve element Element author = retrieveElement(authorFile, cnt); // check for valid value // and check whether element already has an ID if (author != null && !hasAuthorID(cnt)) { // if not, set unique ID-attribute to entry // init variable StringBuilder id = new StringBuilder(""); // add manual timestamp id.append(Tools.getTimeStampWithMilliseconds()).append(settings.getFileName()) .append(String.valueOf(cnt)); // now add id to zettel-element author.setAttribute(ATTRIBUTE_AUTHOR_ID, id.toString()); // and add timestamp attribute author.setAttribute(ATTRIBUTE_AUTHOR_TIMESTAMP, Tools.getTimeStampWithMilliseconds()); } } // iterate all elements for (int cnt = 1; cnt <= getCount(KWCOUNT); cnt++) { // retrieve element Element keyword = retrieveElement(keywordFile, cnt); // check for valid value // and check whether element already has an ID if (keyword != null && !hasKeywordID(cnt)) { // if not, set unique ID-attribute to entry // init variable StringBuilder id = new StringBuilder(""); // add manual timestamp id.append(Tools.getTimeStampWithMilliseconds()).append(settings.getFileName()) .append(String.valueOf(cnt)); // now add id to zettel-element keyword.setAttribute(ATTRIBUTE_KEYWORD_ID, id.toString()); // and add timestamp attribute keyword.setAttribute(ATTRIBUTE_KEYWORD_TIMESTAMP, Tools.getTimeStampWithMilliseconds()); } } // change modified state setModified(true); } /** * This method updates the timestamp-attributes in the data base. The former * XML-elements (timestamp) with the children "created" and "edited" are from database * version 3.4 on simply stored as attribute of each element. */ public void db_updateTimestampAttributes() { // iterate all elements for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // retrieve element Element zettel = retrieveZettel(cnt); // check for valid value if (zettel != null) { // init strings with default values String created = Tools.getTimeStamp(); String edited = ""; // retrieve created-timestamp Element el = zettel.getChild("timestamp").getChild("created"); if (el != null) created = el.getText(); // retrieve edited-timestamp el = zettel.getChild("timestamp").getChild("edited"); if (el != null) edited = el.getText(); // remove old values zettel.removeChild("timestamp"); // and set timestamp as attributes setTimestamp(zettel, created, edited); } } } /** * This method updates the data structure from version 3.3 to version 3.4. With * this change, each Zettel-element gets two new child-elements which refer to the * previous and next entry. With this references, we can reorder entries without * changing their place in the data structure, i.e. the user can re-order entries * in their visible order as they appear in the application, while their "id", i.e. * their place in the data structure does not change. * <br><br> * Futhermore, the root-element gets references to the first and the last entry in that order. */ public void db_updateEntryOrderReferences() { int first = -1; int last = -1; // iterate all elements for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // find first entry in the database that is not deleted if (!isDeleted(cnt)) { // if we found it, store that entry as first element first = cnt; break; } } // iterate all elements for (int cnt = getCount(ZKNCOUNT); cnt > 0; cnt--) { // find last entry in the database that is not deleted if (!isDeleted(cnt)) { // if we found it, store that entry as last element last = cnt; break; } } // set first and last entry, store // them in database setFirstZettel(first); setLastZettel(last); // // iterate all elements // for (int cnt=1; cnt<=getCount(ZKNCOUNT); cnt++) { // // retrieve element // Element zettel = retrieveZettel(cnt); // // here we change the entry's luhmann-numbers (trailing entries) and the // // entry's manual links with the unique IDs // replaceAttributeNrWithID(zettel); // } // this variable stores the number of the last entry // where a reference to a previous entry has been set. // needed for later reference, see below int lastPrevZettel = first; // iterate all elements for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // retrieve element Element zettel = retrieveZettel(cnt); // check for valid value if (zettel != null) { // check whether entry is not deleted if (!isDeleted(cnt)) { // check whether entry is first entry if (cnt == first) { // the last's next entry is this entry setNextZettel(last, cnt); // the previous entry is the last entry setPrevZettel(cnt, last); } // check whether entry is last entry if (cnt == last) { // the last's next entry is the first entry setNextZettel(cnt, first); // the first's previous entry is this (last) entry setPrevZettel(first, cnt); // set this entry's previous zettel setPrevZettel(cnt, lastPrevZettel); // set previous entry's next zettel to this zettel setNextZettel(lastPrevZettel, cnt); } // if entry is neither first nor last entry, but // "in between", set reference from previous entry to this entry if (cnt != first && cnt != last) { // set the last previous entry number as previous entry // of the current entry setPrevZettel(cnt, lastPrevZettel); // the previous entry poins to this entry setNextZettel(lastPrevZettel, cnt); } // and make the current entry as reference for the last // previous-set entry lastPrevZettel = cnt; } } } // // iterate all elements // for (int cnt=1; cnt<=getCount(ZKNCOUNT); cnt++) { // // retrieve element // Element zettel = retrieveZettel(cnt); // // here we change the entry's luhmann-numbers (trailing entries) and the // // entry's manual links with the unique IDs // replaceAttributeIDWithNr(zettel); // } // change modified state setModified(true); } /** * This method replaces all numeral references to other entries * from manual links and trailing entry references with the referenced * entries' IDs. * <br><br> * That means, the trails and manual link child attributes that contain * numbers of other entries will be modified, so these attributes contain * the entries' IDs instead of their numbers. * * @param zettel the entry element where the number references should be replaced with * entry IDs */ private void replaceAttributeNrWithID(Element zettel) { // create string array for attribute iteration String[] attributes = new String[] { ELEMENT_TRAILS, ELEMENT_MANLINKS }; // iterate array for (String attr : attributes) { // check whether entry has luhmann-element if (zettel.getChild(attr) != null) { // get Luhmann-numbers (trailing-entries) String luh = zettel.getChild(attr).getText(); // check whether entry has trailing-numbers if (!luh.isEmpty()) { // split them into an array... String[] luhmann = luh.split(","); // prepare string builder StringBuilder sb = new StringBuilder(""); // if we have trailing numbers, go on if (luhmann != null && luhmann.length > 0) { for (String luhmann1 : luhmann) { // retrieve luhmann number try { // retrieve number of luhmann entry int nr = Integer.parseInt(luhmann1); // get unique ID of that entry String val = getZettelID(nr); // replace numeral reference with ID number if (val != null) { sb.append(val).append(","); } } catch (NumberFormatException ex) { } } // finallay, remove the last "," if (sb.length() > 1) { sb.setLength(sb.length() - 1); } // update attribute zettel.getChild(attr).setText(sb.toString()); } } } } } /** * This method replaces all ID references to other entries * from manual links and trailing entry references with the * entries' number. * <br><br> * That means, the trails and manual link child attributes that have been converted * by the {@link #replaceAttributeNrWithID(org.jdom.Element) replaceAttributeNrWithID} method * will be "resetted", so these attributes contain the entries' numbers again instead of their IDs. * * @param zettel the entry element where the IDs should be replaced with * the entry number */ private void replaceAttributeIDWithNr(Element zettel) { // check whether entry is deleted or not if (isDeleted(zettel)) { return; } // // here we change the entry's luhmann-numbers (trailing entries) and the // entry's manual links with the unique IDs // // create string array for attribute iteration String[] attributes = new String[] { ELEMENT_TRAILS, ELEMENT_MANLINKS }; // iterate array for (String attr : attributes) { // we have to re-convert the unique entry-ID's to their // related entry-numbers, which we do here // first, retrieve list of IDs String[] values = zettel.getChild(attr).getText().split(","); // create variable with re-converted values StringBuilder final_values = new StringBuilder(""); // check for valid values if (values.length > 0) { // iterate all numbers, which are at the moment // still unique IDs instead of numbers (referring to an entry) for (String v : values) { // retrieve entry number of that entry with // the uniqe ID stored in "l" int nr = findZettelFromID(v); // check whether ID was found if (nr != -1) { // append number to stringbuilder final_values.append(String.valueOf(nr)).append(","); } } // check whether we have any values in the stringbuilder // and truncate last comma if (final_values.length() > 0) { final_values.setLength(final_values.length() - 1); } } // set back converted parameters zettel.getChild(attr).setText(final_values.toString()); } } /** * This method returns the number of the next Zettel which is positioned * after the Zettel with the number {@code pos}. This method is needed * for reordering the entries without changing their original number, * i.e. position in the database.<br><br> * This method retrieves the entry-number of the next entry in the * user-defined order, which is stored in the attribute * {@link #ATTRIBUTE_NEXT_ZETTEL} of each {@code zettel} element. * * @param pos The number or position of the current entry. * @return The number of the next entry in the entry-order, or {@code -1} * if no such entry or element exists. */ public int getNextZettel(int pos) { // retrieve current zettel-element Element zettel = retrieveZettel(pos); // check for valid value if (zettel != null) { // retrieve attribute String next = zettel.getAttributeValue(ATTRIBUTE_NEXT_ZETTEL); // check whether we have a valid attribute that refers to the // next entry if (next != null && !next.isEmpty()) { try { // retrieve number int nr = Integer.parseInt(next); // check whether number is within bounds if (nr < 1 || nr > getCount(ZKNCOUNT)) { nr = getFirstZettel(); } // return result return nr; } catch (NumberFormatException ex) { } } } return -1; } /** * This method sets the number of the next Zettel which is positioned * after the Zettel with the number {@code pos}. This method is needed * for reordering the entries without changing their original number, * i.e. position in the database.<br><br> * Sample: * {@code setNextZettel(5,9)} sets the reference to the next entry of * entry 5 to number 9, i.e. entry 5 points to entry 9 as next entry. * * @param pos The number or position of the current entry, where the reference * to the next entry should be set * @param nr the number of the next entry, where the entry {@code pos} should * refer to. */ public void setNextZettel(int pos, int nr) { // retrieve current zettel-element Element zettel = retrieveZettel(pos); // check for valid value if (zettel != null) { // check whether number is within bounds if (nr >= 1 && nr <= getCount(ZKNCOUNT) || -1 == nr /* We need -1 here for restting the value */) { // set attribute zettel.setAttribute(ATTRIBUTE_NEXT_ZETTEL, String.valueOf(nr)); } } } /** * This method returns the number of the first Zettel in the * user-defined order, which is stored in the attribute * {@link #ATTRIBUTE_FIRST_ZETTEL} of the root element {@link #zknFile}. * * @return The number of the first entry in the entry-order, or {@code -1} * if no such entry or element exists. */ public int getFirstZettel() { // check for valid value if (zknFile != null) { // retrieve attribute String first = zknFile.getRootElement().getAttributeValue(ATTRIBUTE_FIRST_ZETTEL); // check whether we have a valid attribute that refers to the // first entry // next entry if (first != null && !first.isEmpty()) { try { // retrieve number int nr = Integer.parseInt(first); // Seit Version 3.1.7: auskommentiert, da Wert auch -1 // sein kann // check whether number is within bounds if (/* nr<1 || */ nr > getCount(ZKNCOUNT)) { nr = 1; } // return result return nr; } catch (NumberFormatException ex) { } } } return -1; } /** * This method checks whether the given entry with the number {@code nr} is the first * entry in the entry order. * * @param nr the number of an entry that should be checked whether it's the first entry in * the order or not. * @return {@code true} if the entry {@code nr} is the first entry in the order, * {@code false otherwise} */ public boolean isFirstZettel(int nr) { return (getFirstZettel() == nr); } /** * This method checks whether the given entry with the number {@code nr} is the last * entry in the entry order. * * @param nr the number of an entry that should be checked whether it's the lasz entry in * the order or not. * @return {@code true} if the entry {@code nr} is the last entry in the order, * {@code false otherwise} */ public boolean isLastZettel(int nr) { return (getLastZettel() == nr); } /** * This method sets the number of the first Zettel in the * user-defined order, which is stored in the attribute * {@link #ATTRIBUTE_FIRST_ZETTEL} of the root element {@link #zknFile}. * * @param nr the number of the first entry in the user-defined entry-order */ public void setFirstZettel(int nr) { // check for valid value if (zknFile != null) { // check whether number is within bounds if ((nr >= 1 && nr <= getCount(ZKNCOUNT)) || -1 == nr /* We need this to reset this value */) { // set attribute zknFile.getRootElement().setAttribute(ATTRIBUTE_FIRST_ZETTEL, String.valueOf(nr)); } } } /** * This method returns the number of the previous Zettel which is positioned * before the Zettel with the number {@code pos}. This method is needed * for reordering the entries without changing their original number, * i.e. position in the database.<br><br> * This method retrieves the entry-number of the previous entry in the * user-defined order, which is stored in the attribute * {@link #ATTRIBUTE_PREV_ZETTEL} of each {@code zettel} element. * * @param pos The number or position of the current entry. * @return The number of the previous entry in the entry-order, or {@code -1} * if no such entry or element exists. */ public int getPrevZettel(int pos) { // retrieve current zettel-element Element zettel = retrieveZettel(pos); // check for valid value if (zettel != null) { // retrieve attribute String next = zettel.getAttributeValue(ATTRIBUTE_PREV_ZETTEL); // check whether we have a valid attribute that refers to the // next entry if (next != null && !next.isEmpty()) { try { // retrieve number int nr = Integer.parseInt(next); // check whether number is within bounds if (nr < 1 || nr > getCount(ZKNCOUNT)) { nr = getLastZettel(); } // return result return nr; } catch (NumberFormatException ex) { } } } return -1; } /** * This method sets the number of the previous Zettel which is positioned * before the Zettel with the number {@code pos}. This method is needed * for reordering the entries without changing their original number, * i.e. position in the database.<br><br> * Sample: * {@code setPrevZettel(5,9)} sets the reference to the previous entry of * entry 5 to number 9, i.e. entry 5 points to entry 9 as previous entry. * * @param pos The number or position of the current entry, where the reference * to the previous entry should be set * @param nr the number of the previous entry, where the entry {@code pos} should * refer to. */ public void setPrevZettel(int pos, int nr) { // retrieve current zettel-element Element zettel = retrieveZettel(pos); // check for valid value if (zettel != null) { // check whether number is within bounds if (nr >= 1 && nr <= getCount(ZKNCOUNT) || -1 == nr /* We need -1 here for restting the value */) { // set attribute zettel.setAttribute(ATTRIBUTE_PREV_ZETTEL, String.valueOf(nr)); } } } /** * This method returns the number of the last Zettel in the * user-defined order, which is stored in the attribute * {@link #ATTRIBUTE_LAST_ZETTEL} of the root element {@link #zknFile}. * * @return The number of the last entry in the entry-order, or {@code -1} * if no such entry or element exists. */ public int getLastZettel() { // check for valid value if (zknFile != null) { // retrieve attribute String last = zknFile.getRootElement().getAttributeValue(ATTRIBUTE_LAST_ZETTEL); // check whether we have a valid attribute that refers to the // first entry // next entry if (last != null && !last.isEmpty()) { try { // retrieve number int nr = Integer.parseInt(last); // Seit Version 3.1.7: auskommentiert, da Wert auch -1 // sein kann // check whether number is within bounds if (/* nr<1 || */ nr > getCount(ZKNCOUNT)) { nr = 1; } // return result return nr; } catch (NumberFormatException ex) { } } } return -1; } /** * This method sets the number of the last Zettel in the * user-defined order, which is stored in the attribute * {@link #ATTRIBUTE_LAST_ZETTEL} of the root element {@link #zknFile}. * * @param nr the number of the last entry in the user-defined entry-order */ public void setLastZettel(int nr) { // check for valid value if (zknFile != null) { // check whether number is within bounds if ((nr >= 1 && nr <= getCount(ZKNCOUNT)) || -1 == nr /* We need this to reset this value */) { // set attribute zknFile.getRootElement().setAttribute(ATTRIBUTE_LAST_ZETTEL, String.valueOf(nr)); } } } /** * This method changes the timestamp attributes of an entry. * * @param zettel the entry-element in the XML-database. * @param created the new value for the creation timestamp of an entry as string. * Use {@code null} if this attribute value should not be changed. * @param edited the new value for the last modification timestamp of an entry as string. * Use {@code null} if this attribute value should not be changed. */ public void setTimestamp(Element zettel, String created, String edited) { // check for valid parameter if (zettel != null) { // check for valid parameter and change created attribute if (created != null) { zettel.setAttribute(ATTRIBUTE_TIMESTAMP_CREATED, created); } // check for valid parameter and change edited attribute if (edited != null) { zettel.setAttribute(ATTRIBUTE_TIMESTAMP_EDITED, edited); } setModified(true); } } /** * This method changes the timestamp attributes of an entry. * * @param zettel the entry-element in the XML-database. * @param edited the new value for the last modification timestamp of an entry as string. * Use {@code null} if this attribute value should not be changed. */ public void setTimestampEdited(Element zettel, String edited) { // check for valid parameter if (zettel != null) { // check for valid parameter and change edited attribute if (edited != null) { zettel.setAttribute(ATTRIBUTE_TIMESTAMP_EDITED, edited); } setModified(true); } } /** * This method changes the timestamp attributes of an entry. * * @param zettel the entry-element in the XML-database. * @param created the new value for the creation timestamp of an entry as string. * Use {@code null} if this attribute value should not be changed. */ public void setTimestampCreated(Element zettel, String created) { // check for valid parameter if (zettel != null) { // check for valid parameter and change created attribute if (created != null) { zettel.setAttribute(ATTRIBUTE_TIMESTAMP_CREATED, created); } setModified(true); } } /** * This method changes the timestamp attributes of an entry. * * @param nr the entry-number of the entry which timestamp should be changed * @param edited the new value for the last modification timestamp of an entry as string. * Use {@code null} if this attribute value should not be changed. */ public void setTimestampEdited(int nr, String edited) { setTimestampEdited(retrieveZettel(nr), edited); } /** * This method changes the timestamp attributes of an entry. * * @param nr the entry-number of the entry which timestamp should be changed * @param created the new value for the creation timestamp of an entry as string. * Use {@code null} if this attribute value should not be changed. */ public void setTimestampCreated(int nr, String created) { setTimestampCreated(retrieveZettel(nr), created); } /** * This method changes the timestamp attributes of an entry. * * @param nr the entry-number of the entry which timestamp should be changed * @param created the new value for the creation timestamp of an entry as string. * Use {@code null} if this attribute value should not be changed. * @param edited the new value for the last modification timestamp of an entry as string. * Use {@code null} if this attribute value should not be changed. */ public void setTimestamp(int nr, String created, String edited) { setTimestamp(retrieveZettel(nr), created, edited); } /** * This method creates a new unique ID for an entry and adds it as ID-attribute to * the zettel-XML-element. * <br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(ZKNCOUNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param nr the number of the entry that shoud receive a new ID. * the position of the element, ranged from 1 to {@link #getCount(int) getCount(ZKNCOUNT)} */ public void setZettelID(int nr) { setZettelID(retrieveZettel(nr)); } /** * This method creates a new unique ID for an entry and adds it as ID-attribute to * the zettel-XML-element. * <br><br> * * @param zettel the entry that shoud receive a new ID, as XML-element */ public void setZettelID(Element zettel) { // check for valid value if (zettel != null) { // now add id to zettel-element zettel.setAttribute(ATTRIBUTE_ZETTEL_ID, Tools.createZknID(settings.getFileName())); // change modified state setModified(true); } } /** * This method retrieves the new unique ID for an entry. * <br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(ZKNCOUNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param nr the number of the entry of which ID shoud be retrieved. * the position of the element, ranged from 1 to {@link #getCount(int) getCount(ZKNCOUNT)} * @return the unique ID of an entry as string-value, or {@code null} if no ID was found. */ public String getZettelID(int nr) { return getZettelID(retrieveZettel(nr)); } /** * This method retrieves the new unique ID for an entry. * <br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(ZKNCOUNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param zettel the entry-element which ID shoud be retrieved. * @return the unique ID of an entry as string-value, or {@code null} if no ID was found. */ public String getZettelID(Element zettel) { // check for valid value if (zettel != null) { // now add id to zettel-element String id = zettel.getAttributeValue(ATTRIBUTE_ZETTEL_ID); // check for valid value if (id != null && !id.isEmpty()) { return id; } } return null; } /** * This method stores the ID number of the last new added entry, so we * always know which entry was latest added to the data base. * * @param zettel the entry-element, which ID should be saved. */ public void setLastAddedZettelID(Element zettel) { lastAddedZettelID = getZettelID(zettel); } /** * This method returns the ID of the last new added entry that * was added to the data base. * * @return the ID of the last new added entry that was added to the data base. */ public String getLastAddedZettelID() { return lastAddedZettelID; } /** * This method retrieves the new unique ID for an author-element. * <br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(AUCOUNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param nr the number of the author-element of which ID shoud be retrieved. * the position of the element, ranged from 1 to {@link #getCount(int) getCount(AUCOUNT)} * @return the unique ID of an author-element as string-value, or {@code null} if no ID was found. */ public String getAuthorID(int nr) { // retrieve element Element author = retrieveElement(authorFile, nr); // check for valid value if (author != null) { // now add id to zettel-element String id = author.getAttributeValue(ATTRIBUTE_AUTHOR_ID); // check for valid value if (id != null && !id.isEmpty()) { return id; } } return null; } /** * This method retrieves the unique ID for an author-element. * * @param au the string value (case sensitive) of the author-element of which ID shoud be retrieved. * @return the unique ID of a author-element as string-value, or {@code null} if no ID was found. */ public String getAuthorID(String au) { // check for valid value if (au != null && !au.isEmpty()) { // find author int pos = findAuthorInDatabase(au); // check whether author was found or not if (pos != -1) { // return author ID return getAuthorID(pos); } } return null; } /** * This method retrieves the new unique ID for a keyword-element. * <br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(KWCOUNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param nr the number of the keyword-element of which ID shoud be retrieved. * the position of the element, ranged from 1 to {@link #getCount(int) getCount(KWCOUNT)} * @return the unique ID of a keyword-element as string-value, or {@code null} if no ID was found. */ public String getKeywordID(int nr) { // retrieve element Element keyword = retrieveElement(keywordFile, nr); // check for valid value if (keyword != null) { // now add id to zettel-element String id = keyword.getAttributeValue(ATTRIBUTE_KEYWORD_ID); // check for valid value if (id != null && !id.isEmpty()) { return id; } } return null; } /** * This method retrieves the unique ID for a keyword-element. * * @param kw the string value (case sensitive) of the keyword-element of which ID shoud be retrieved. * @return the unique ID of a keyword-element as string-value, or {@code null} if no ID was found. */ public String getKeywordID(String kw) { // check for valid value if (kw != null && !kw.isEmpty()) { // find keyword int pos = findKeywordInDatabase(kw); // check whether kw was found or not if (pos != -1) { // return keyword ID return getKeywordID(pos); } } return null; } /** * This method finds / retrieves the entry-number of an entry with the * unique ID {@code id} that is passed as parameter.<br><br> * <b>Caution!</b> This method might be very time consuming, if the data base * is huge. Consider using this method only within a background task! * <br><br> * <i>This method is identical to {@link #getZettelNumberFromID(java.lang.String)}.</i> * * @param id the unique entry-ID of an entry, which entry-number should be retrieved * @return the entry-number of that entry with the unique ID {@code id}, or {@code -1} * if no such entry was found or an invalid parameter was passed. */ public int findZettelFromID(String id) { // check for valid parameter if (id != null && !id.isEmpty()) { // go through all entries in database for (int cnt = 1; cnt <= getCount(ZKNCOUNT); cnt++) { // retrieve entry's unique ID String zettelid = getZettelID(cnt); // check for valid ID if (zettelid != null) { // when parameter-ID matches found ID, return entry-number if (id.equals(zettelid)) { return cnt; } } } } return -1; } /** * This method finds / retrieves the author-number of an author-element with the * unique ID {@code id} that is passed as parameter.<br><br> * <b>Caution!</b> This method might be very time consuming, if the data base * is huge. Consider using this method only within a background task! * <br><br> * <i>This method is identical to {@link #getAuthorNumberFromID(java.lang.String)}.</i> * * @param id the unique author-ID of an entry, which author -number should be retrieved * @return the author-number of that author-element with the unique ID {@code id}, or {@code -1} * if no such entry was found or an invalid parameter was passed. */ public int findAuthorFromID(String id) { // check for valid parameter if (id != null && !id.isEmpty()) { // go through all entries in database for (int cnt = 1; cnt <= getCount(AUCOUNT); cnt++) { // retrieve author's unique ID String authorid = getAuthorID(cnt); // check for valid ID if (authorid != null) { // when parameter-ID matches found ID, return entry-number if (id.equals(authorid)) { return cnt; } } } } return -1; } /** * This method finds / retrieves the keyword-number of a keyword-element with the * unique ID {@code id} that is passed as parameter.<br><br> * <b>Caution!</b> This method might be very time consuming, if the data base * is huge. Consider using this method only within a background task! * <br><br> * <i>This method is identical to {@link #getKeywordNumberFromID(java.lang.String)}.</i> * * @param id the unique keyword-ID of an entry, which keyword-number should be retrieved * @return the keyword-number of that keyword-element with the unique ID {@code id}, or {@code -1} * if no such entry was found or an invalid parameter was passed. */ public int findKeywordFromID(String id) { // check for valid parameter if (id != null && !id.isEmpty()) { // go through all entries in database for (int cnt = 1; cnt <= getCount(KWCOUNT); cnt++) { // retrieve author's unique ID String keywordid = getKeywordID(cnt); // check for valid ID if (keywordid != null) { // when parameter-ID matches found ID, return entry-number if (id.equals(keywordid)) { return cnt; } } } } return -1; } /** * This method finds / retrieves the entry-number of an entry with the * unique ID {@code id} that is passed as parameter.<br><br> * <b>Caution!</b> This method might be very time consuming, if the data base * is huge. Consider using this method only within a background task! * <br><br> * <i>This method is identical to {@link #findZettelFromID(java.lang.String)}.</i> * * @param id the unique entry-ID of an entry, which entry-number should be retrieved * @return the entry-number of that entry with the unique ID {@code id}, or {@code -1} * if no such entry was found or an invalid parameter was passed. */ public int getZettelNumberFromID(String id) { return findZettelFromID(id); } /** * This method finds / retrieves the author-number of an author-element with the * unique ID {@code id} that is passed as parameter.<br><br> * <b>Caution!</b> This method might be very time consuming, if the data base * is huge. Consider using this method only within a background task! * <br><br> * <i>This method is identical to {@link #findAuthorFromID(java.lang.String)}.</i> * * @param id the unique author-ID of an entry, which author -number should be retrieved * @return the author-number of that author-element with the unique ID {@code id}, or {@code -1} * if no such entry was found or an invalid parameter was passed. */ public int getAuthorNumberFromID(String id) { return findAuthorFromID(id); } /** * This method finds / retrieves the keyword-number of a keyword-element with the * unique ID {@code id} that is passed as parameter.<br><br> * <b>Caution!</b> This method might be very time consuming, if the data base * is huge. Consider using this method only within a background task! * <br><br> * <i>This method is identical to {@link #findKeywordFromID(java.lang.String)}.</i> * * @param id the unique keyword-ID of an entry, which keyword-number should be retrieved * @return the keyword-number of that keyword-element with the unique ID {@code id}, or {@code -1} * if no such entry was found or an invalid parameter was passed. */ public int getKeywordNumberFromID(String id) { return findKeywordFromID(id); } /** * This method checks whether an entry with the unique ID {@code id} already * exists in the current data base or not. * * @param id the unique entry-ID of an entry which should be checked for existence. * @return {@code true} when an entry with the unique ID {@code id} already exists in the data file, * {@code false} otherwise. */ public boolean zettelExists(String id) { return (findZettelFromID(id) != -1); } /** * This method checks whether an entry with the number {@code nr} * exists in the current data base or not. * * @param nr the number of an entry which should be checked for existence. * @return {@code true} when an entry with the unique ID {@code id} already exists in the data file, * {@code false} otherwise. */ public boolean zettelExists(int nr) { return (retrieveZettel(nr) != null); } /** * This method checks whether a keyword with the unique ID {@code id} already * exists in the current data base or not. * <br><br> * In case you want to look for an existing keyword according to identical * Strings (not ID!), use {@link #getKeywordPosition(java.lang.String)}. * * @param id the unique keyword-ID of a keyword-element which should be checked for existence. * @return {@code true} when a keyword with the unique ID {@code id} already exists in the data file, * {@code false} otherwise. */ public boolean keywordExists(String id) { return (findKeywordFromID(id) != -1); } /** * This method checks whether an author with the unique ID {@code id} already * exists in the current data base or not. * <br><br> * In case you want to look for an existing author according to identical * Strings (not ID!), use {@link #getAuthorPosition(java.lang.String)}. * * @param id the unique author-ID of an author-element which should be checked for existence. * @return {@code true} when a author with the unique ID {@code id} already exists in the data file, * {@code false} otherwise. */ public boolean authorExists(String id) { return (findAuthorFromID(id) != -1); } /** * This method checks whether the entry with the number {@code nr} has a unique * ID assigned to it or not. * <br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(ZKNCOUNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param nr the number of the entry that shoud be checked for a valid ID. * the position of the element, ranged from 1 to {@link #getCount(int) getCount(ZKNCOUNT)} * @return {@code true} if an ID was found, {@code false} otherwise. */ public boolean hasZettelID(int nr) { // retrieve element Element zettel = retrieveZettel(nr); // check for valid value if (zettel != null) { // now add id to zettel-element String id = zettel.getAttributeValue(ATTRIBUTE_ZETTEL_ID); // check for valid value if (id != null && !id.isEmpty()) { return true; } } return false; } /** * This method checks whether an author-element with the number {@code nr} has a unique * ID assigned to it or not. * <br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(AUCouNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param nr the number of the author-element that shoud be checked for a valid ID. * the position of the element, ranged from 1 to {@link #getCount(int) getCount(AUCOUNT)} * @return {@code true} if an ID was found, {@code false} otherwise. */ public boolean hasAuthorID(int nr) { // retrieve element Element author = retrieveElement(authorFile, nr); // check for valid value if (author != null) { // now add id to author-element String id = author.getAttributeValue(ATTRIBUTE_AUTHOR_ID); // check for valid value if (id != null && !id.isEmpty()) { return true; } } return false; } /** * This method checks whether a keyword-element with the number {@code nr} has a unique * ID assigned to it or not. * <br><br> * <b>Caution!</b> The position {@code pos} is a value from <b>1</b> to * {@link #getCount(int) getCount(KWCOUNT)} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param nr the number of the keyword-element that shoud be checked for a valid ID. * the position of the element, ranged from 1 to {@link #getCount(int) getCount(KWCOUNT)} * @return {@code true} if an ID was found, {@code false} otherwise. */ public boolean hasKeywordID(int nr) { // retrieve element Element keyword = retrieveElement(keywordFile, nr); // check for valid value if (keyword != null) { // now add id to keyword-element String id = keyword.getAttributeValue(ATTRIBUTE_KEYWORD_ID); // check for valid value if (id != null && !id.isEmpty()) { return true; } } return false; } /** * Use this method to indicate whether saving the data file was ok, or whether an error * occured. This method is typically used in the saving-task, see * {@link de.danielluedecke.zettelkasten.tasks.SaveFileTask SaveFileTask}. * * @param val {@code true} when saving the data file was ok, {@code false} if an error occured. */ public void setSaveOk(boolean val) { saveOk = val; } /** * Indicates whether saving the data file was ok, or whether an error * occured. See {@link de.danielluedecke.zettelkasten.tasks.SaveFileTask SaveFileTask}. * @return {@code true} when saving the data file was ok, {@code false} if an error occured. */ public boolean isSaveOk() { return saveOk; } /** * This method extracts all occurences of possible form-tags (Laws of Form) * from an entry with the index-number {@code nr} and returns them as an * array list of strings. * * @param nr the index-number of the entry where the forms should be retrieved * @return an array list with all form-tags of that entry as strings, or {@code null} * if no form tag was found. */ public ArrayList<String> getZettelForms(int nr) { // retrieve entry content return Tools.getFormsFromString(getZettelContent(nr)); } /** * This methods check whether formtags within an entry already exist as image files, * and if not, it does create these image files. * * @param content the content of an entry where the form-tags will be extracted * and the form-images will be created. */ private void createFormImagesFromContent(String content) { // retrieve form-tags from content ArrayList<String> dummy = Tools.getFormsFromString(content); // copy them to a string array String[] formtags = dummy.toArray(new String[dummy.size()]); // iterate all form-tags for (String formimg : formtags) { // create new instance for creating form images CMakeFormImage newFormImage = new CMakeFormImage(this, settings, formimg); // create form image newFormImage.createFormImage(); // check for errors if (!newFormImage.isSaveImgOk()) { zknframe.showErrorIcon(); } } } /** * This method moves an entry in the displayed entry order. The entry keeps its number * and will not be moved within the XML data file. only the pointer referenced (see * {@link #ATTRIBUTE_NEXT_ZETTEL} and {@link #ATTRIBUTE_PREV_ZETTEL}) are changed. * * @param entryToMove The number of that entry that should be moved to another position (source). * @param insertAfter The number of that entry after which the {@code entryToMove} should * be inserted (destination). */ public void moveEntry(int entryToMove, int insertAfter) { // check if equal if (entryToMove == insertAfter) { return; } // check whether insertAfter is a valid destination if (isDeleted(insertAfter) || !zettelExists(insertAfter)) { return; } // first, check whether the entry is inserted after that entry // where it already is sorted in. if yes, don't move anything if (getPrevZettel(entryToMove) == insertAfter) { return; } // now, connect those entries which originally refered // to the move entry // therefor we need the refrenced to the previous // and next entry of the move entry int moveprev = getPrevZettel(entryToMove); int movenext = getNextZettel(entryToMove); // now connect these entries, so the move entry // is "cut out" of the entry order setNextZettel(moveprev, movenext); setPrevZettel(movenext, moveprev); // check, whether moved entry was the first entry // if so, the entry following the move entry is now the new first entry if (isFirstZettel(entryToMove)) { setFirstZettel(movenext); } // check, whether moved entry was the last entry // if so, the entry before the move entry is now the new last entry if (isLastZettel(entryToMove)) { setLastZettel(moveprev); } // now, insert entry at new position // therefore we need to "disconnect" those entries where the move entry should be inserted // therefor we need the refrence to the next entry of the insertAfter entry int nextBehindEntryToMove = getNextZettel(insertAfter); setPrevZettel(nextBehindEntryToMove, entryToMove); setNextZettel(insertAfter, entryToMove); // and insert the new entry setPrevZettel(entryToMove, insertAfter); setNextZettel(entryToMove, nextBehindEntryToMove); // check whether insertAfter was the last entry in the order if (isLastZettel(insertAfter)) { setLastZettel(entryToMove); } // set modified flag setModified(true); // title list is out of date setTitlelistUpToDate(false); } /** * This method moves several entries in their displayed entry order. The entries keeps their number * and will not be moved within the XML data file. only the pointer referenced (see * {@link #ATTRIBUTE_NEXT_ZETTEL} and {@link #ATTRIBUTE_PREV_ZETTEL}) are changed. * * @param entriesToMove An array with numbers of those entries that should be moved to another position (source). * @param insertAfter The number of that entry after which the {@code entriesToMove} should * be inserted (destination). */ public void moveEntries(int[] entriesToMove, int insertAfter) { // check for valid values if (entriesToMove != null && entriesToMove.length > 0) { // go through the array, but from last to first element. // since each entry is inserted after "insertAfter", we keep // the order of the moved entries by inserting them from last // to first entry for (int cnt = entriesToMove.length - 1; cnt >= 0; cnt--) { // move each entry moveEntry(entriesToMove[cnt], insertAfter); } } } /** * This method moves several entries in their displayed entry order. The entries keeps their number * and will not be moved within the XML data file. only the pointer referenced (see * {@link #ATTRIBUTE_NEXT_ZETTEL} and {@link #ATTRIBUTE_PREV_ZETTEL}) are changed. * * @param entriesToMove An array with numbers in String format of those entries that should be moved to another position (source). * @param insertAfter The number of that entry after which the {@code entriesToMove} should * be inserted (destination). */ public void moveEntries(String[] entriesToMove, int insertAfter) { // check for valid values if (entriesToMove != null && entriesToMove.length > 0) { // go through the array, but from last to first element. // since each entry is inserted after "insertAfter", we keep // the order of the moved entries by inserting them from last // to first entry for (int cnt = entriesToMove.length - 1; cnt >= 0; cnt--) { // move each entry try { moveEntry(Integer.parseInt(entriesToMove[cnt]), insertAfter); } catch (NumberFormatException ex) { } } } } /** * This method tries to find a parent-level follower of the entry * with the number {@code nr}. * <br/><br/> * <b>Caution!</b> This method might be time consuming. Consider using it only * in a background thread. * * <br/><br/><b>Caution!</b> The position {@code nr} is a value from <b>1</b> to * {@link #getCount(int) getCount()} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param nr the index number of the entry * @return the number of the "luhmann"-parent of the entry {@code nr}, or {@code -1} if * the entry {@code nr} has no luhmann (follower) parent */ public int findParentlLuhmann(int nr) { // init find value boolean found = true; // int retval = -1; // while (found) { // indicates, whether any luhmann parent was found boolean innerLoopFound = false; // counter for looping through entries int cnt = 1; // get current entry number as string String currentEntry = String.valueOf(nr); // go through complete data set while (!innerLoopFound && cnt <= getCount(Daten.ZKNCOUNT)) { // get the luhmann-numbers of each entry String[] lnrs = getLuhmannNumbers(cnt).split(","); // now check each number for the occurence of the current entry number for (String l : lnrs) { // when one of the luhmann-numbers equals the current entry number... if (l.equals(currentEntry)) { // we found a parent nr = retval = cnt; // indicate that parent was found innerLoopFound = true; break; } } // inceare loop counter cnt++; } // when all entries have been checked and no parent was found // leave complete routine and return result. if (!innerLoopFound) { found = false; } } return retval; } /** * This method retrieves all follower and follower's follower of the entry * {@code zettelpos} and stores the index numbers in the global integer array * {@link #allLuhmannNumbers allLuhmannNumbers} * <br/><b>Caution!</b> The position {@code zettelpos} is a value from <b>1</b> to * {@link #getCount(int) getCount()} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param zettelpos the index number of the entry */ private void retrieveAllLuhmannNumbers(int zettelpos) { // get the text from the luhmann-numbers String lnr = getLuhmannNumbers(zettelpos); // if we have any luhmann-numbers, go on... if (!lnr.isEmpty()) { // copy all values to an array String[] lnrs = lnr.split(","); // go throughh array of current luhmann-numbers for (String exist : lnrs) { // append value to result array try { // therefor, convert it to int int nr = Integer.parseInt(exist); // add value to results if (!allLuhmannNumbers.contains(nr)) { allLuhmannNumbers.add(nr); } // check whether more deeper luhmann-value exists, by re-calling this method // again and go through a recusrive loop retrieveAllLuhmannNumbers(nr); } catch (NumberFormatException ex) { } } } } /** * This method retrieves all follower and follower's follower of the entry * {@code zettelpos} and returns the index numbers as integer array. * <br/><b>Caution!</b> The position {@code zettelpos} is a value from <b>1</b> to * {@link #getCount(int) getCount()} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param zettelpos the index number of the entry * @return all follower and follower's follower of the entry * {@code zettelpos} as integer array */ public int[] getAllLuhmannNumbers(int zettelpos) { // reset result array allLuhmannNumbers.clear(); // call method to retrieve all luhmann numbers retrieveAllLuhmannNumbers(zettelpos); // create int arrey int[] arr = new int[allLuhmannNumbers.size()]; // copy all values into array for (int cnt = 0; cnt < allLuhmannNumbers.size(); cnt++) { arr[cnt] = allLuhmannNumbers.get(cnt); } // return result return arr; } /** * This method checks whether the entry {@code zettelpos} has follower / trails / luhmann * numbers or not. * <br/><b>Caution!</b> The position {@code zettelpos} is a value from <b>1</b> to * {@link #getCount(int) getCount()} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param zettelpos the index number of the entry * @return {@code true} if the entry {@code zettelpos} has follower / trails / luhmann * numbers, {@code false} if not. */ public boolean hasLuhmannNumbers(int zettelpos) { // retrieve luhmann numbers String lnr = getLuhmannNumbers(zettelpos); return (lnr != null && !lnr.isEmpty()); } /** * This method checks whether the entry {@code zettelpos} has manual links / references * to other entries or not. * <br/><b>Caution!</b> The position {@code zettelpos} is a value from <b>1</b> to * {@link #getCount(int) getCount()} - in contrary * to usual array handling where the range is from 0 to (size-1). * * @param zettelpos the index number of the entry * @return {@code true} if the entry {@code zettelpos} has manual links / references to other * entries, {@code false} if not. */ public boolean hasManLinks(int zettelpos) { // retrieve manual links int[] ml = getManualLinks(zettelpos); return (ml != null && ml.length > 0); } /** * This method extracts manual links from an entry's content that have been added via * the NewEntryFrame.<br><br> * All manual link tags {@code [z #number]text[/z]} will be scanned and the numbers * (references to other entries) are extracted. All manual links are returned as integer list. * * @param dummy the content from an entry as String * @return all manual links inside the manual-link-tag {@code [z]} as integer list */ private List<Integer> extractManualLinksFromContent(String dummy) { // save manual links List<Integer> manlinknumbers = new ArrayList<Integer>(); try { // create foot note patterm Pattern p = Pattern.compile("\\[z ([^\\[]*)\\](.*?)\\[/z\\]"); // create matcher Matcher m = p.matcher(dummy); // check for occurences while (m.find()) { // if we found something, we have two groups // the 2nd groups contains the manlink number as string int ml = Integer.parseInt(dummy.substring(m.start(m.groupCount() - 1), m.end(m.groupCount() - 1))); // add to result list manlinknumbers.add(ml); } } catch (PatternSyntaxException ex) { } catch (IndexOutOfBoundsException ex) { } catch (NumberFormatException ex) { } return manlinknumbers; } /** * Creates a sorted String with comma separated values of manual links. Manual links are stored * in this String format in the XML database. * * @param manlinknumbers (extracted) manual links as integer list. Use {@link #extractManualLinksFromContent(java.lang.String)} * to retrieve manual links from an entry's content as integer list * @return a String containing all manual links, so this String can be stored as child-element in the * XML database (see {@link #ELEMENT_MANLINKS}). */ private String retrievePreparedManualLinksFromContent(List<Integer> manlinknumbers) { // add them, if we have any if (manlinknumbers != null && !manlinknumbers.isEmpty()) { // convert to array Integer[] i = manlinknumbers.toArray(new Integer[manlinknumbers.size()]); // sort array Arrays.sort(i); StringBuilder sb = new StringBuilder(""); // and add it for (int ml : i) { sb.append(String.valueOf(ml)).append(","); } // remove last comma if (sb.length() > 1) sb.setLength(sb.length() - 1); // add to element return (sb.toString()); } else { return (""); } } }