/src/main/java/net/sf/jabref/Util.java
Java | 3514 lines | 2282 code | 311 blank | 921 comment | 583 complexity | e0f03a63a637dde3a4cf26453b617b0c MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- /* Copyright (C) 2003-2012 JabRef contributors.
- 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 2 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, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
- // created by : Morten O. Alver 2003
- package net.sf.jabref;
- import java.awt.BorderLayout;
- import java.awt.CardLayout;
- import java.awt.Color;
- import java.awt.Component;
- import java.awt.Desktop;
- import java.awt.Dimension;
- import java.awt.Font;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.*;
- import java.net.MalformedURLException;
- import java.net.URI;
- import java.net.URISyntaxException;
- import java.net.URL;
- import java.net.URLConnection;
- import java.net.URLDecoder;
- import java.nio.charset.Charset;
- import java.nio.charset.CharsetEncoder;
- import java.text.NumberFormat;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Calendar;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.StringTokenizer;
- import java.util.TreeSet;
- import java.util.Vector;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import javax.swing.Action;
- import javax.swing.ActionMap;
- import javax.swing.BorderFactory;
- import javax.swing.Box;
- import javax.swing.InputMap;
- import javax.swing.JButton;
- import javax.swing.JComponent;
- import javax.swing.JDialog;
- import javax.swing.JFrame;
- import javax.swing.JLabel;
- import javax.swing.JOptionPane;
- import javax.swing.JPanel;
- import javax.swing.JProgressBar;
- import javax.swing.JRootPane;
- import javax.swing.JScrollPane;
- import javax.swing.JTextArea;
- import javax.swing.SwingUtilities;
- import javax.swing.undo.AbstractUndoableEdit;
- import javax.swing.undo.UndoableEdit;
- import net.sf.jabref.autocompleter.AbstractAutoCompleter;
- import net.sf.jabref.export.SaveSession;
- import net.sf.jabref.export.layout.Layout;
- import net.sf.jabref.export.layout.LayoutHelper;
- import net.sf.jabref.external.ExternalFileType;
- import net.sf.jabref.external.ExternalFileTypeEntryEditor;
- import net.sf.jabref.external.RegExpFileSearch;
- import net.sf.jabref.external.UnknownExternalFileType;
- import net.sf.jabref.groups.AbstractGroup;
- import net.sf.jabref.groups.KeywordGroup;
- import net.sf.jabref.gui.FileListEntry;
- import net.sf.jabref.gui.FileListEntryEditor;
- import net.sf.jabref.gui.FileListTableModel;
- import net.sf.jabref.labelPattern.LabelPatternUtil;
- import net.sf.jabref.net.URLDownload;
- import net.sf.jabref.undo.NamedCompound;
- import net.sf.jabref.undo.UndoableFieldChange;
- import net.sf.jabref.util.FileNameCleaner;
- import com.jgoodies.forms.builder.DefaultFormBuilder;
- import com.jgoodies.forms.layout.FormLayout;
- /**
- * utility functions
- */
- public class Util {
- /**
- * A static Object for date formatting. Please do not create the object
- * here, because there are some references from the Globals class.....
- *
- */
- private static SimpleDateFormat dateFormatter = null;
- /*
- * Colors are defined here.
- *
- */
- public static Color fieldsCol = new Color(180, 180, 200);
- /*
- * Integer values for indicating result of duplicate check (for entries):
- *
- */
- final static int TYPE_MISMATCH = -1, NOT_EQUAL = 0, EQUAL = 1, EMPTY_IN_ONE = 2,
- EMPTY_IN_TWO = 3, EMPTY_IN_BOTH = 4;
-
- final static NumberFormat idFormat;
- public static Pattern remoteLinkPattern = Pattern.compile("[a-z]+://.*");
- public static int MARK_COLOR_LEVELS = 6,
- MAX_MARKING_LEVEL = MARK_COLOR_LEVELS-1,
- IMPORT_MARK_LEVEL = MARK_COLOR_LEVELS;
- public static Pattern markNumberPattern = Pattern.compile(JabRefPreferences.getInstance().MARKING_WITH_NUMBER_PATTERN);
- static {
- idFormat = NumberFormat.getInstance();
- idFormat.setMinimumIntegerDigits(8);
- idFormat.setGroupingUsed(false);
- }
- public static int getMinimumIntegerDigits(){
- return idFormat.getMinimumIntegerDigits();
- }
- public static void pr(String s) {
- Globals.logger(s);
- }
- public static String nCase(String s) {
- // Make first character of String uppercase, and the
- // rest lowercase.
- if (s.length() > 1)
- return s.substring(0, 1).toUpperCase() + s.substring(1, s.length()).toLowerCase();
- else
- return s.toUpperCase();
- }
- public static String checkName(String s) {
- // Append '.bib' to the string unless it ends with that.
- if (s.length() < 4 || !s.substring(s.length() - 4).equalsIgnoreCase(".bib")) {
- return s + ".bib";
- }
- return s;
- }
- private static int idCounter = 0;
- public synchronized static String createNeutralId() {
- return idFormat.format(idCounter++);
- }
- /**
- * This method sets the location of a Dialog such that it is centered with
- * regard to another window, but not outside the screen on the left and the
- * top.
- */
- public static void placeDialog(java.awt.Dialog diag, java.awt.Container win) {
- diag.setLocationRelativeTo(win);
- }
- /**
- * This method translates a field or string from Bibtex notation, with
- * possibly text contained in " " or { }, and string references,
- * concatenated by '#' characters, into Bibkeeper notation, where string
- * references are enclosed in a pair of '#' characters.
- */
- public static String parseField(String content) {
-
- if (content.length() == 0)
- return content;
-
- String[] strings = content.split("#");
- StringBuffer result = new StringBuffer();
- for (String string : strings) {
- String s = string.trim();
- if (s.length() > 0) {
- char c = s.charAt(0);
- // String reference or not?
- if (c == '{' || c == '"') {
- result.append(shaveString(string));
- } else {
- // This part should normally be a string reference, but if it's
- // a pure number, it is not.
- String s2 = shaveString(s);
- try {
- Integer.parseInt(s2);
- // If there's no exception, it's a number.
- result.append(s2);
- } catch (NumberFormatException ex) {
- // otherwise append with hashes...
- result.append("#").append(s2).append("#");
- }
- }
- }
- }
- return result.toString();
- }
- /**
- * Will return the publication date of the given bibtex entry in conformance
- * to ISO 8601, i.e. either YYYY or YYYY-MM.
- *
- * @param entry
- * @return will return the publication date of the entry or null if no year
- * was found.
- */
- public static String getPublicationDate(BibtexEntry entry) {
- Object o = entry.getField("year");
- if (o == null)
- return null;
- String year = toFourDigitYear(o.toString());
- o = entry.getField("month");
- if (o != null) {
- int month = Util.getMonthNumber(o.toString());
- if (month != -1) {
- return year + "-" + (month + 1 < 10 ? "0" : "") + (month + 1);
- }
- }
- return year;
- }
- public static String shaveString(String s) {
- // returns the string, after shaving off whitespace at the beginning
- // and end, and removing (at most) one pair of braces or " surrounding
- // it.
- if (s == null)
- return null;
- char ch, ch2;
- int beg = 0, end = s.length();
- // We start out assuming nothing will be removed.
- boolean begok = false, endok = false;
- while (!begok) {
- if (beg < s.length()) {
- ch = s.charAt(beg);
- if (Character.isWhitespace(ch))
- beg++;
- else
- begok = true;
- } else
- begok = true;
- }
- while (!endok) {
- if (end > beg + 1) {
- ch = s.charAt(end - 1);
- if (Character.isWhitespace(ch))
- end--;
- else
- endok = true;
- } else
- endok = true;
- }
- if (end > beg + 1) {
- ch = s.charAt(beg);
- ch2 = s.charAt(end - 1);
- if (((ch == '{') && (ch2 == '}')) || ((ch == '"') && (ch2 == '"'))) {
- beg++;
- end--;
- }
- }
- s = s.substring(beg, end);
- return s;
- }
-
- public static String rtrim(String s) {
- return s.replaceAll("\\s+$","");
- }
- /**
- * This method returns a String similar to the one passed in, except that it
- * is molded into a form that is acceptable for bibtex.
- *
- * Watch-out that the returned string might be of length 0 afterwards.
- *
- * @param key
- * mayBeNull
- */
- public static String checkLegalKey(String key) {
- if (key == null)
- return null;
- if (!JabRefPreferences.getInstance().getBoolean("enforceLegalBibtexKey")) {
- // User doesn't want us to enforce legal characters. We must still look
- // for whitespace and some characters such as commas, since these would
- // interfere with parsing:
- StringBuilder newKey = new StringBuilder();
- for (int i = 0; i < key.length(); i++) {
- char c = key.charAt(i);
- if (!Character.isWhitespace(c) && (c != '{') && (c != '\\') && (c != '"')
- && (c != '}') && (c != ','))
- newKey.append(c);
- }
- return newKey.toString();
- }
- StringBuilder newKey = new StringBuilder();
- for (int i = 0; i < key.length(); i++) {
- char c = key.charAt(i);
- if (!Character.isWhitespace(c) && (c != '#') && (c != '{') && (c != '\\') && (c != '"')
- && (c != '}') && (c != '~') && (c != ',') && (c != '^') && (c != '\''))
- newKey.append(c);
- }
- // Replace non-english characters like umlauts etc. with a sensible
- // letter or letter combination that bibtex can accept.
- String newKeyS = replaceSpecialCharacters(newKey.toString());
- return newKeyS;
- }
- /**
- * Replace non-english characters like umlauts etc. with a sensible letter
- * or letter combination that bibtex can accept. The basis for replacement
- * is the HashMap GLobals.UNICODE_CHARS.
- */
- public static String replaceSpecialCharacters(String s) {
- for (Map.Entry<String, String> chrAndReplace : Globals.UNICODE_CHARS.entrySet()){
- s = s.replaceAll(chrAndReplace.getKey(), chrAndReplace.getValue());
- }
- return s;
- }
- static public String _wrap2(String in, int wrapAmount) {
- // The following line cuts out all whitespace and replaces them with
- // single
- // spaces:
- // in = in.replaceAll("[ ]+"," ").replaceAll("[\\t]+"," ");
- // StringBuffer out = new StringBuffer(in);
- StringBuffer out = new StringBuffer(in.replaceAll("[ \\t\\r]+", " "));
- int p = in.length() - wrapAmount;
- int lastInserted = -1;
- while (p > 0) {
- p = out.lastIndexOf(" ", p);
- if (p <= 0 || p <= 20)
- break;
- int lbreak = out.indexOf("\n", p);
- System.out.println(lbreak + " " + lastInserted);
- if ((lbreak > p) && ((lastInserted >= 0) && (lbreak < lastInserted))) {
- p = lbreak - wrapAmount;
- } else {
- out.insert(p, "\n\t");
- lastInserted = p;
- p -= wrapAmount;
- }
- }
- return out.toString();
- }
- static public String wrap2(String in, int wrapAmount) {
- return net.sf.jabref.imports.FieldContentParser.wrap(in, wrapAmount);
- }
- static public String __wrap2(String in, int wrapAmount) {
- // The following line cuts out all whitespace except line breaks, and
- // replaces
- // with single spaces. Line breaks are padded with a tab character:
- StringBuffer out = new StringBuffer(in.replaceAll("[ \\t\\r]+", " "));
- int p = 0;
- // int lastInserted = -1;
- while (p < out.length()) {
- int q = out.indexOf(" ", p + wrapAmount);
- if ((q < 0) || (q >= out.length()))
- break;
- int lbreak = out.indexOf("\n", p);
- // System.out.println(lbreak);
- if ((lbreak > p) && (lbreak < q)) {
- p = lbreak + 1;
- int piv = lbreak + 1;
- if ((out.length() > piv) && !(out.charAt(piv) == '\t'))
- out.insert(piv, "\n\t");
- } else {
- // System.out.println(q+" "+out.length());
- out.deleteCharAt(q);
- out.insert(q, "\n\t");
- p = q + 1;
- }
- }
- return out.toString();// .replaceAll("\n", "\n\t");
- }
- public static TreeSet<String> findDeliminatedWordsInField(BibtexDatabase db, String field,
- String deliminator) {
- TreeSet<String> res = new TreeSet<String>();
-
- for (String s : db.getKeySet()){
- BibtexEntry be = db.getEntryById(s);
- Object o = be.getField(field);
- if (o != null) {
- String fieldValue = o.toString().trim();
- StringTokenizer tok = new StringTokenizer(fieldValue, deliminator);
- while (tok.hasMoreTokens())
- res.add(nCase(tok.nextToken().trim()));
- }
- }
- return res;
- }
- /**
- * Returns a HashMap containing all words used in the database in the given
- * field type. Characters in <code>remove</code> are not included.
- *
- * @param db
- * a <code>BibtexDatabase</code> value
- * @param field
- * a <code>String</code> value
- * @param remove
- * a <code>String</code> value
- * @return a <code>HashSet</code> value
- */
- public static TreeSet<String> findAllWordsInField(BibtexDatabase db, String field, String remove) {
- TreeSet<String> res = new TreeSet<String>();
- StringTokenizer tok;
- for (String s : db.getKeySet()){
- BibtexEntry be = db.getEntryById(s);
- Object o = be.getField(field);
- if (o != null) {
- tok = new StringTokenizer(o.toString(), remove, false);
- while (tok.hasMoreTokens())
- res.add(nCase(tok.nextToken().trim()));
- }
- }
- return res;
- }
- /**
- * Finds all authors' last names in all the given fields for the given database.
- * @param db The database.
- * @param fields The fields to look in.
- * @return a set containing the names.
- */
- public static Set<String> findAuthorLastNames(BibtexDatabase db, List<String> fields) {
- Set<String> res = new TreeSet<String>();
- for (String s : db.getKeySet()){
- BibtexEntry be = db.getEntryById(s);
- for (String field : fields) {
- String val = be.getField(field);
- if ((val != null) && (val.length() > 0)) {
- AuthorList al = AuthorList.getAuthorList(val);
- for (int i=0; i<al.size(); i++) {
- AuthorList.Author a = al.getAuthor(i);
- String lastName = a.getLast();
- if ((lastName != null) && (lastName.length() > 0))
- res.add(lastName);
- }
- }
- }
- }
- return res;
- }
-
- /**
- * Takes a String array and returns a string with the array's elements
- * delimited by a certain String.
- *
- * @param strs
- * String array to convert.
- * @param delimiter
- * String to use as delimiter.
- * @return Delimited String.
- */
- public static String stringArrayToDelimited(String[] strs, String delimiter) {
- if ((strs == null) || (strs.length == 0))
- return "";
- if (strs.length == 1)
- return strs[0];
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < strs.length - 1; i++) {
- sb.append(strs[i]);
- sb.append(delimiter);
- }
- sb.append(strs[strs.length - 1]);
- return sb.toString();
- }
- /**
- * Takes a delimited string, splits it and returns
- *
- * @param names
- * a <code>String</code> value
- * @return a <code>String[]</code> value
- */
- public static String[] delimToStringArray(String names, String delimiter) {
- if (names == null)
- return null;
- return names.split(delimiter);
- }
- /**
- * Creates a substring from a text
- *
- * @param text
- * @param i
- * @param terminateOnEndBraceOnly
- * @return
- */
- public static String getPart(String text, int i, boolean terminateOnEndBraceOnly) {
- char c;
- int count = 0;
- StringBuffer part = new StringBuffer();
- // advance to first char and skip whitespace
- i++;
- while (i < text.length() && Character.isWhitespace(text.charAt(i))){
- i++;
- }
- // then grab whathever is the first token (counting braces)
- while (i < text.length()){
- c = text.charAt(i);
- if (!terminateOnEndBraceOnly && count == 0 && Character.isWhitespace(c)) {
- i--; // end argument and leave whitespace for further
- // processing
- break;
- }
- if (c == '}' && --count < 0)
- break;
- else if (c == '{')
- count++;
- part.append(c);
- i++;
- }
- return part.toString();
- }
- /**
- * Open a http/pdf/ps viewer for the given link string.
- */
- public static void openExternalViewer(MetaData metaData, String link, String fieldName)
- throws IOException {
- if (fieldName.equals("ps") || fieldName.equals("pdf")) {
- // Find the default directory for this field type:
- String[] dir = metaData.getFileDirectory(fieldName);
- File file = expandFilename(link, dir);
- // Check that the file exists:
- if ((file == null) || !file.exists()) {
- throw new IOException(Globals.lang("File not found") + " (" + fieldName + "): '"
- + link + "'.");
- }
- link = file.getCanonicalPath();
- // Use the correct viewer even if pdf and ps are mixed up:
- String[] split = file.getName().split("\\.");
- if (split.length >= 2) {
- if (split[split.length - 1].equalsIgnoreCase("pdf"))
- fieldName = "pdf";
- else if (split[split.length - 1].equalsIgnoreCase("ps")
- || (split.length >= 3 && split[split.length - 2].equalsIgnoreCase("ps")))
- fieldName = "ps";
- }
- } else if (fieldName.equals("doi")) {
- fieldName = "url";
-
- // sanitizing is done below at the treatment of "URL"
- // in sanatizeUrl a doi-link is correctly treated
- } else if (fieldName.equals("eprint")) {
- fieldName = "url";
- link = sanitizeUrl(link);
- // Check to see if link field already contains a well formated URL
- if (!link.startsWith("http://")) {
- link = Globals.ARXIV_LOOKUP_PREFIX + link;
- }
- }
- if (fieldName.equals("url")) { // html
- try {
- link = sanitizeUrl(link);
- ExternalFileType fileType = Globals.prefs.getExternalFileTypeByExt("html");
- openExternalFilePlatformIndependent(fileType, link);
- } catch (IOException e) {
- System.err.println(Globals.lang("Error_opening_file_'%0'.", link));
- e.printStackTrace();
- }
- } else if (fieldName.equals("ps")) {
- try {
- if (Globals.ON_MAC) {
- ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("ps");
- String viewer = type != null ? type.getOpenWith() : Globals.prefs.get("psviewer");
- String[] cmd = { "/usr/bin/open", "-a", viewer, link };
- Runtime.getRuntime().exec(cmd);
- } else if (Globals.ON_WIN) {
- openFileOnWindows(link, true);
- /*
- * cmdArray[0] = Globals.prefs.get("psviewer"); cmdArray[1] =
- * link; Process child = Runtime.getRuntime().exec(
- * cmdArray[0] + " " + cmdArray[1]);
- */
- } else {
- ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("ps");
- String viewer = type != null ? type.getOpenWith() : "xdg-open";
- String[] cmdArray = new String[2];
- cmdArray[0] = viewer;
- cmdArray[1] = link;
- Runtime.getRuntime().exec(cmdArray);
- }
- } catch (IOException e) {
- System.err.println("An error occured on the command: "
- + Globals.prefs.get("psviewer") + " " + link);
- }
- } else if (fieldName.equals("pdf")) {
- try {
- if (Globals.ON_MAC) {
- ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("pdf");
- String viewer = type != null ? type.getOpenWith() : Globals.prefs.get("psviewer");
- String[] cmd = { "/usr/bin/open", "-a", viewer, link };
- Runtime.getRuntime().exec(cmd);
- } else if (Globals.ON_WIN) {
- openFileOnWindows(link, true);
- /*
- * String[] spl = link.split("\\\\"); StringBuffer sb = new
- * StringBuffer(); for (int i = 0; i < spl.length; i++) { if
- * (i > 0) sb.append("\\"); if (spl[i].indexOf(" ") >= 0)
- * spl[i] = "\"" + spl[i] + "\""; sb.append(spl[i]); }
- * //pr(sb.toString()); link = sb.toString();
- *
- * String cmd = "cmd.exe /c start " + link;
- *
- * Process child = Runtime.getRuntime().exec(cmd);
- */
- } else {
- ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("pdf");
- String viewer = type != null ? type.getOpenWith() : Globals.prefs.get("psviewer");
- String[] cmdArray = new String[2];
- cmdArray[0] = viewer;
- cmdArray[1] = link;
- // Process child = Runtime.getRuntime().exec(cmdArray[0]+"
- // "+cmdArray[1]);
- Runtime.getRuntime().exec(cmdArray);
- }
- } catch (IOException e) {
- e.printStackTrace();
- System.err.println("An error occured on the command: "
- + Globals.prefs.get("pdfviewer") + " #" + link);
- System.err.println(e.getMessage());
- }
- } else {
- System.err
- .println("Message: currently only PDF, PS and HTML files can be opened by double clicking");
- }
- }
- /**
- * Opens a file on a Windows system, using its default viewer.
- *
- * @param link
- * The file name.
- * @param localFile
- * true if it is a local file, not an URL.
- * @throws IOException
- */
- public static void openFileOnWindows(String link, boolean localFile) throws IOException {
- /*
- * if (localFile) { String[] spl = link.split("\\\\"); StringBuffer sb =
- * new StringBuffer(); for (int i = 0; i < spl.length; i++) { if (i > 0)
- * sb.append("\\"); if (spl[i].indexOf(" ") >= 0) spl[i] = "\"" + spl[i] +
- * "\""; sb.append(spl[i]); } link = sb.toString(); }
- */
- link = link.replaceAll("&", "\"&\"").replaceAll(" ", "\" \"");
- // Bug fix for:
- // http://sourceforge.net/tracker/index.php?func=detail&aid=1489454&group_id=92314&atid=600306
- String cmd;
- if (Globals.osName.startsWith("Windows 9")) {
- cmd = "command.com /c start " + link;
- } else {
- cmd = "cmd.exe /c start " + link;
- }
- Runtime.getRuntime().exec(cmd);
- }
- /**
- * Opens a file on a Windows system, using the given application.
- *
- * @param link The file name.
- * @param application Link to the app that opens the file.
- * @throws IOException
- */
- public static void openFileWithApplicationOnWindows(String link, String application)
- throws IOException {
- link = link.replaceAll("&", "\"&\"").replaceAll(" ", "\" \"");
- Runtime.getRuntime().exec(application + " " + link);
- }
- /**
- * Open an external file, attempting to use the correct viewer for it.
- *
- * @param metaData
- * The MetaData for the database this file belongs to.
- * @param link
- * The file name.
- * @return false if the link couldn't be resolved, true otherwise.
- */
- public static boolean openExternalFileAnyFormat(final MetaData metaData, String link,
- final ExternalFileType fileType) throws IOException {
- boolean httpLink = false;
- if (remoteLinkPattern.matcher(link.toLowerCase()).matches()) {
- httpLink = true;
- }
- /*if (link.toLowerCase().startsWith("file://")) {
- link = link.substring(7);
- }
- final String ln = link;
- if (remoteLinkPattern.matcher(link.toLowerCase()).matches()) {
- (new Thread(new Runnable() {
- public void run() {
- openRemoteExternalFile(metaData, ln, fileType);
- }
- })).start();
- return true;
- }*/
- //boolean httpLink = link.toLowerCase().startsWith("http:")
- // || link.toLowerCase().startsWith("ftp:");
-
- // For other platforms we'll try to find the file type:
- File file = new File(link);
- if (!httpLink) {
- File tmp = expandFilename(metaData, link);
- if (tmp != null)
- file = tmp;
- }
- // Check if we have arrived at a file type, and either an http link or an existing file:
- if ((httpLink || file.exists()) && (fileType != null)) {
- // Open the file:
- String filePath = httpLink ? link : file.getPath();
- openExternalFilePlatformIndependent(fileType, filePath);
- return true;
- } else {
- return false;
- // No file matched the name, or we didn't know the file type.
- }
- }
- private static void openExternalFilePlatformIndependent(ExternalFileType fileType, String filePath) throws IOException {
- if (Globals.ON_MAC) {
- // Use "-a <application>" if the app is specified, and just "open <filename>" otherwise:
- String[] cmd = ((fileType.getOpenWith() != null) && (fileType.getOpenWith().length() > 0)) ?
- new String[] { "/usr/bin/open", "-a", fileType.getOpenWith(), filePath } :
- new String[] { "/usr/bin/open", filePath };
- Runtime.getRuntime().exec(cmd);
- } else if (Globals.ON_WIN) {
- if ((fileType.getOpenWith() != null) && (fileType.getOpenWith().length() > 0)) {
- // Application is specified. Use it:
- openFileWithApplicationOnWindows(filePath, fileType.getOpenWith());
- } else
- openFileOnWindows(filePath, true);
- } else {
- // Use the given app if specified, and the universal "xdg-open" otherwise:
- String[] openWith;
- if ((fileType.getOpenWith() != null) && (fileType.getOpenWith().length() > 0))
- openWith = fileType.getOpenWith().split(" ");
- else
- openWith = new String[] {"xdg-open"};
- String[] cmdArray = new String[openWith.length+1];
- System.arraycopy(openWith, 0, cmdArray, 0, openWith.length);
- cmdArray[cmdArray.length-1] = filePath;
- Runtime.getRuntime().exec(cmdArray);
- }
- }
- public static void openRemoteExternalFile(final MetaData metaData,
- final String link, final ExternalFileType fileType) {
- File temp = null;
- try {
- temp = File.createTempFile("jabref-link", "."+fileType.getExtension());
- temp.deleteOnExit();
- System.out.println("Downloading to '"+temp.getPath()+"'");
- URLDownload ud = new URLDownload(null, new URL(link), temp);
- ud.download();
- System.out.println("Done");
- } catch (MalformedURLException ex) {
- ex.printStackTrace();
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- final String ln = temp.getPath();
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- try {
- openExternalFileAnyFormat(metaData, ln, fileType);
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- }
- });
- }
- public static boolean openExternalFileUnknown(JabRefFrame frame, BibtexEntry entry, MetaData metaData,
- String link, UnknownExternalFileType fileType) throws IOException {
- String cancelMessage = Globals.lang("Unable to open file.");
- String[] options = new String[] {Globals.lang("Define '%0'", fileType.getName()),
- Globals.lang("Change file type"), Globals.lang("Cancel")};
- String defOption = options[0];
- int answer = JOptionPane.showOptionDialog(frame, Globals.lang("This external link is of the type '%0', which is undefined. What do you want to do?",
- fileType.getName()),
- Globals.lang("Undefined file type"), JOptionPane.YES_NO_CANCEL_OPTION,
- JOptionPane.QUESTION_MESSAGE, null, options, defOption);
- if (answer == JOptionPane.CANCEL_OPTION) {
- frame.output(cancelMessage);
- return false;
- }
- else if (answer == JOptionPane.YES_OPTION) {
- // User wants to define the new file type. Show the dialog:
- ExternalFileType newType = new ExternalFileType(fileType.getName(), "", "", "", "new");
- ExternalFileTypeEntryEditor editor = new ExternalFileTypeEntryEditor(frame, newType);
- editor.setVisible(true);
- if (editor.okPressed()) {
- // Get the old list of types, add this one, and update the list in prefs:
- List<ExternalFileType> fileTypes = new ArrayList<ExternalFileType>();
- ExternalFileType[] oldTypes = Globals.prefs.getExternalFileTypeSelection();
- Collections.addAll(fileTypes, oldTypes);
- fileTypes.add(newType);
- Collections.sort(fileTypes);
- Globals.prefs.setExternalFileTypes(fileTypes);
- // Finally, open the file:
- return openExternalFileAnyFormat(metaData, link, newType);
- } else {
- // Cancelled:
- frame.output(cancelMessage);
- return false;
- }
- }
- else {
- // User wants to change the type of this link.
- // First get a model of all file links for this entry:
- FileListTableModel tModel = new FileListTableModel();
- String oldValue = entry.getField(GUIGlobals.FILE_FIELD);
- tModel.setContent(oldValue);
- FileListEntry flEntry = null;
- // Then find which one we are looking at:
- for (int i=0; i<tModel.getRowCount(); i++) {
- FileListEntry iEntry = tModel.getEntry(i);
- if (iEntry.getLink().equals(link)) {
- flEntry = iEntry;
- break;
- }
- }
- if (flEntry == null) {
- // This shouldn't happen, so I'm not sure what to put in here:
- throw new RuntimeException("Could not find the file list entry "+link+" in "+entry.toString());
- }
- FileListEntryEditor editor = new FileListEntryEditor(frame, flEntry, false, true, metaData);
- editor.setVisible(true, false);
- if (editor.okPressed()) {
- // Store the changes and add an undo edit:
- String newValue = tModel.getStringRepresentation();
- UndoableFieldChange ce = new UndoableFieldChange(entry, GUIGlobals.FILE_FIELD,
- oldValue, newValue);
- entry.setField(GUIGlobals.FILE_FIELD, newValue);
- frame.basePanel().undoManager.addEdit(ce);
- frame.basePanel().markBaseChanged();
- // Finally, open the link:
- return openExternalFileAnyFormat(metaData, flEntry.getLink(), flEntry.getType());
- } else {
- // Cancelled:
- frame.output(cancelMessage);
- return false;
- }
- }
- }
- /**
- * Make sure an URL is "portable", in that it doesn't contain bad characters
- * that break the open command in some OSes.
- *
- * A call to this method will also remove \\url{} enclosings and clean DOI links.
- *
- * @param link :the URL to sanitize.
- * @return Sanitized URL
- */
- public static String sanitizeUrl(String link) {
- link = link.trim();
- // First check if it is enclosed in \\url{}. If so, remove
- // the wrapper.
- if (link.startsWith("\\url{") && link.endsWith("}"))
- link = link.substring(5, link.length() - 1);
- if (link.matches("^doi:/*.*")){
- // Remove 'doi:'
- link = link.replaceFirst("^doi:/*", "");
- link = Globals.DOI_LOOKUP_PREFIX + link;
- }
- // converts doi-only link to full http address
- // Morten Alver 6 Nov 2012: this extracts a nonfunctional DOI from some complete
- // http addresses (e.g. http://onlinelibrary.wiley.com/doi/10.1002/rra.999/abstract, where
- // the trailing "/abstract" is included but doesn't lead to a resolvable DOI).
- // To prevent mangling of working URLs I'm disabling this check if the link is already
- // a full http link:
- if (checkForPlainDOI(link) && !link.startsWith("http://")) {
- link = Globals.DOI_LOOKUP_PREFIX + getDOI(link);
- }
- link = link.replaceAll("\\+", "%2B");
- try {
- link = URLDecoder.decode(link, "UTF-8");
- } catch (UnsupportedEncodingException ignored) {
- }
- /**
- * Fix for: [ 1574773 ] sanitizeUrl() breaks ftp:// and file:///
- *
- * http://sourceforge.net/tracker/index.php?func=detail&aid=1574773&group_id=92314&atid=600306
- */
- try {
- return new URI(null, link, null).toASCIIString();
- } catch (URISyntaxException e) {
- return link;
- }
- }
- /**
- * Searches the given directory and subdirectories for a pdf file with name
- * as given + ".pdf"
- */
- public static String findPdf(String key, String extension, String directory, OpenFileFilter off) {
- // String filename = key + "."+extension;
- /*
- * Simon Fischer's patch for replacing a regexp in keys before
- * converting to filename:
- *
- * String regex = Globals.prefs.get("basenamePatternRegex"); if ((regex !=
- * null) && (regex.trim().length() > 0)) { String replacement =
- * Globals.prefs.get("basenamePatternReplacement"); key =
- * key.replaceAll(regex, replacement); }
- */
- if (!directory.endsWith(System.getProperty("file.separator")))
- directory += System.getProperty("file.separator");
- String found = findInDir(key, directory, off, 0);
- if (found != null)
- return found.substring(directory.length());
- else
- return null;
- }
- public static Map<BibtexEntry, List<File>> findAssociatedFiles(Collection<BibtexEntry> entries, Collection<String> extensions, Collection<File> directories){
- HashMap<BibtexEntry, List<File>> result = new HashMap<BibtexEntry, List<File>>();
-
- // First scan directories
- Set<File> filesWithExtension = findFiles(extensions, directories);
-
- // Initialize Result-Set
- for (BibtexEntry entry : entries){
- result.put(entry, new ArrayList<File>());
- }
- boolean exactOnly = Globals.prefs.getBoolean(JabRefPreferences.AUTOLINK_EXACT_KEY_ONLY);
- // Now look for keys
- nextFile:
- for (File file : filesWithExtension){
-
- String name = file.getName();
- int dot = name.lastIndexOf('.');
- // First, look for exact matches:
- for (BibtexEntry entry : entries){
- String citeKey = entry.getCiteKey();
- if ((citeKey != null) && (citeKey.length() > 0)) {
- if (dot > 0) {
- if (name.substring(0, dot).equals(citeKey)) {
- result.get(entry).add(file);
- continue nextFile;
- }
- }
- }
- }
- // If we get here, we didn't find any exact matches. If non-exact
- // matches are allowed, try to find one:
- if (!exactOnly) {
- for (BibtexEntry entry : entries){
- String citeKey = entry.getCiteKey();
- if ((citeKey != null) && (citeKey.length() > 0)) {
- if (name.startsWith(citeKey)){
- result.get(entry).add(file);
- continue nextFile;
- }
- }
- }
- }
- }
-
- return result;
- }
-
- public static Set<File> findFiles(Collection<String> extensions, Collection<File> directories) {
- Set<File> result = new HashSet<File>();
-
- for (File directory : directories){
- result.addAll(findFiles(extensions, directory));
- }
-
- return result;
- }
- private static Collection<? extends File> findFiles(Collection<String> extensions, File directory) {
- Set<File> result = new HashSet<File>();
-
- File[] children = directory.listFiles();
- if (children == null)
- return result; // No permission?
- for (File child : children){
- if (child.isDirectory()) {
- result.addAll(findFiles(extensions, child));
- } else {
-
- String extension = getFileExtension(child);
-
- if (extension != null){
- if (extensions.contains(extension)){
- result.add(child);
- }
- }
- }
- }
-
- return result;
- }
- /**
- * Returns the extension of a file or null if the file does not have one (no . in name).
- *
- * @param file
- *
- * @return The extension, trimmed and in lowercase.
- */
- public static String getFileExtension(File file) {
- String name = file.getName();
- int pos = name.lastIndexOf('.');
- String extension = ((pos >= 0) && (pos < name.length() - 1)) ? name.substring(pos + 1)
- .trim().toLowerCase() : null;
- return extension;
- }
- /**
- * New version of findPdf that uses findFiles.
- *
- * The search pattern will be read from the preferences.
- *
- * The [extension]-tags in this pattern will be replace by the given
- * extension parameter.
- *
- */
- public static String findPdf(BibtexEntry entry, String extension, String directory) {
- return findPdf(entry, extension, new String[] { directory });
- }
- /**
- * Convenience method for findPDF. Can search multiple PDF directories.
- */
- public static String findPdf(BibtexEntry entry, String extension, String[] directories) {
- String regularExpression;
- if (Globals.prefs.getBoolean(JabRefPreferences.USE_REG_EXP_SEARCH_KEY)) {
- regularExpression = Globals.prefs.get(JabRefPreferences.REG_EXP_SEARCH_EXPRESSION_KEY);
- } else {
- regularExpression = Globals.prefs
- .get(JabRefPreferences.DEFAULT_REG_EXP_SEARCH_EXPRESSION_KEY);
- }
- regularExpression = regularExpression.replaceAll("\\[extension\\]", extension);
- return findFile(entry, null, directories, regularExpression, true);
- }
- /**
- * Convenience menthod for findPDF. Searches for a file of the given type.
- * @param entry The BibtexEntry to search for a link for.
- * @param fileType The file type to search for.
- * @return The link to the file found, or null if not found.
- */
- public static String findFile(BibtexEntry entry, ExternalFileType fileType, List<String> extraDirs) {
- List<String> dirs = new ArrayList<String>();
- dirs.addAll(extraDirs);
- if (Globals.prefs.hasKey(fileType.getExtension()+"Directory")) {
- dirs.add(Globals.prefs.get(fileType.getExtension()+"Directory"));
- }
- String [] directories = dirs.toArray(new String[dirs.size()]);
- return findPdf(entry, fileType.getExtension(), directories);
- }
- /**
- * Searches the given directory and file name pattern for a file for the
- * bibtexentry.
- *
- * Used to fix:
- *
- * http://sourceforge.net/tracker/index.php?func=detail&aid=1503410&group_id=92314&atid=600309
- *
- * Requirements:
- * - Be able to find the associated PDF in a set of given directories.
- * - Be able to return a relative path or absolute path.
- * - Be fast.
- * - Allow for flexible naming schemes in the PDFs.
- *
- * Syntax scheme for file:
- * <ul>
- * <li>* Any subDir</li>
- * <li>** Any subDir (recursiv)</li>
- * <li>[key] Key from bibtex file and database</li>
- * <li>.* Anything else is taken to be a Regular expression.</li>
- * </ul>
- *
- * @param entry
- * non-null
- * @param database
- * non-null
- * @param directory
- * A set of root directories to start the search from. Paths are
- * returned relative to these directories if relative is set to
- * true. These directories will not be expanded or anything. Use
- * the file attribute for this.
- * @param file
- * non-null
- *
- * @param relative
- * whether to return relative file paths or absolute ones
- *
- * @return Will return the first file found to match the given criteria or
- * null if none was found.
- */
- public static String findFile(BibtexEntry entry, BibtexDatabase database, String[] directory,
- String file, boolean relative) {
- for (String aDirectory : directory) {
- String result = findFile(entry, database, aDirectory, file, relative);
- if (result != null) {
- return result;
- }
- }
- return null;
- }
- /**
- * Removes optional square brackets from the string s
- *
- * @param s
- * @return
- */
- public static String stripBrackets(String s) {
- int beginIndex = (s.startsWith("[") ? 1 : 0);
- int endIndex = (s.endsWith("]") ? s.length() - 1 : s.length());
- return s.substring(beginIndex, endIndex);
- }
- public static ArrayList<String[]> parseMethodsCalls(String calls) throws RuntimeException {
- ArrayList<String[]> result = new ArrayList<String[]>();
- char[] c = calls.toCharArray();
- int i = 0;
- while (i < c.length) {
- int start = i;
- if (Character.isJavaIdentifierStart(c[i])) {
- i++;
- while (i < c.length && (Character.isJavaIdentifierPart(c[i]) || c[i] == '.')) {
- i++;
- }
- if (i < c.length && c[i] == '(') {
- String method = calls.substring(start, i);
- // Skip the brace
- i++;
- if (i < c.length){
- if (c[i] == '"'){
- // Parameter is in format "xxx"
- // Skip "
- i++;
- int startParam = i;
- i++;
- boolean escaped = false;
- while (i + 1 < c.length &&
- !(!escaped && c[i] == '"' && c[i + 1] == ')')) {
- if (c[i] == '\\') {
- escaped = !escaped;
- }
- else
- escaped = false;
- i++;
- }
- String param = calls.substring(startParam, i);
-
- result.add(new String[] { method, param });
- } else {
- // Parameter is in format xxx
- int startParam = i;
- while (i < c.length && c[i] != ')') {
- i++;
- }
- String param = calls.substring(startParam, i);
- result.add(new String[] { method, param });
- }
- } else {
- // Incorrecly terminated open brace
- result.add(new String[] { method });
- }
- } else {
- String method = calls.substring(start, i);
- result.add(new String[] { method });
- }
- }
- i++;
- }
- return result;
- }
- /**
- * Accepts a string like [author:lower] or [title:abbr] or [auth],
- * whereas the first part signifies the bibtex-field to get, or the key generator
- * field marker to use, while the others are the modifiers that will be applied.
- *
- * @param fieldAndFormat
- * @param entry
- * @param database
- * @return
- */
- public static String getFieldAndFormat(String fieldAndFormat, BibtexEntry entry,
- BibtexDatabase database) {
- fieldAndFormat = stripBrackets(fieldAndFormat);
- int colon = fieldAndFormat.indexOf(':');
- String beforeColon, afterColon;
- if (colon == -1) {
- beforeColon = fieldAndFormat;
- afterColon = null;
- } else {
- beforeColon = fieldAndFormat.substring(0, colon);
- afterColon = fieldAndFormat.substring(colon + 1);
- }
- beforeColon = beforeColon.trim();
- if (beforeColon.length() == 0) {
- return null;
- }
- String fieldValue = BibtexDatabase.getResolvedField(beforeColon, entry, database);
- // If no field value was found, try to interpret it as a key generator field marker:
- if (fieldValue == null)
- fieldValue = LabelPatternUtil.makeLabel(entry, beforeColon);
- if (fieldValue == null)
- return null;
- if (afterColon == null || afterColon.length() == 0)
- return fieldValue;
- String[] parts = afterColon.split(":");
- fieldValue = LabelPatternUtil.applyModifiers(fieldValue, parts, 0);
-
- return fieldValue;
- }
- /**
- * Convenience function for absolute search.
- *
- * Uses findFile(BibtexEntry, BibtexDatabase, (String)null, String, false).
- */
- public static String findFile(BibtexEntry entry, BibtexDatabase database, String file) {
- return findFile(entry, database, (String) null, file, false);
- }
- /**
- * Internal Version of findFile, which also accepts a current directory to
- * base the search on.
- *
- */
- public static String findFile(BibtexEntry entry, BibtexDatabase database, String directory,
- String file, boolean relative) {
- File root;
- if (directory == null) {
- root = new File(".");
- } else {
- root = new File(directory);
- }
- if (!root.exists())
- return null;
- String found = findFile(entry, database, root, file);
- if (directory == null || !relative) {
- return found;
- }
- if (found != null) {
- try {
- /**
- * [ 1601651 ] PDF subdirectory - missing first character
- *
- * http://sourceforge.net/tracker/index.php?func=detail&aid=1601651&group_id=92314&atid=600306
- */
- // Changed by M. Alver 2007.01.04:
- // Remove first character if it is a directory separator character:
- String tmp = found.substring(root.getCanonicalPath().length());
- if ((tmp.length() > 1) && (tmp.charAt(0) == File.separatorChar))
- tmp = tmp.substring(1);
- return tmp;
- //return found.substring(root.getCanonicalPath().length());
- } catch (IOException e) {
- return null;
- }
- }
- return null;
- }
- /**
- * The actual work-horse. Will find absolute filepaths starting from the
- * given directory using the given regular expression string for search.
- */
- protected static String findFile(BibtexEntry entry, BibtexDatabase database, File directory,
- String file) {
- if (file.startsWith("/")) {
- directory = new File(".");
- file = file.substring(1);
- }
- // Escape handling...
- Matcher m = Pattern.compile("([^\\\\])\\\\([^\\\\])").matcher(file);
- StringBuffer s = new StringBuffer();
- while (m.find()) {
- m.appendReplacement(s, m.group(1) + "/" + m.group(2));
- }
- m.appendTail(s);
- file = s.toString();
- String[] fileParts = file.split("/");
- if (fileParts.length == 0)
- return null;
- if (fileParts.length > 1) {
- for (int i = 0; i < fileParts.length - 1; i++) {
- String dirToProcess = fileParts[i];
- dirToProcess = expandBrackets(dirToProcess, entry, database);
- if (dirToProcess.matches("^.:$")) { // Windows Drive Letter
- directory = new File(dirToProcess + "/");
- continue;
- }
- if (dirToProcess.equals(".")) { // Stay in current directory
- continue;
- }
- if (dirToProcess.equals("..")) {
- directory = new File(directory.getParent());
- continue;
- }
- if (dirToProcess.equals("*")) { // Do for all direct subdirs
- File[] subDirs = directory.listFiles();
- if (subDirs == null)
- return null; // No permission?
- String restOfFileString = join(fileParts, "/", i + 1, fileParts.length);
- for (File subDir : subDirs) {
- if (subDir.isDirectory()) {
- String result = findFile(entry, database, subDir,
- restOfFileString);
- if (result != null)
- return result;
- }
- }
- return null;
- }
- // Do for all direct and indirect subdirs
- if (dirToProcess.equals("**")) {
- List<File> toDo = new LinkedList<File>();
- toDo.add(directory);
- String restOfFileString = join(fileParts, "/", i + 1, fileParts.length);
- // Before checking the subdirs, we first check the current
- // dir
- String result = findFile(entry, database, directory, restOfFileString);
- if (result != null)
- return result;
- while (!toDo.isEmpty()) {
- // Get all subdirs of each of the elements found in toDo
- File[] subDirs = toDo.remove(0).listFiles();
- if (subDirs == null) // No permission?
- continue;
- toDo.addAll(Arrays.asList(subDirs));
- for (File subDir : subDirs) {
- if (!subDir.isDirectory())
- continue;
- result = findFile(entry, database, subDir, restOfFileString);
- if (result != null)
- return result;
- }
- }
- // We already did the currentDirectory
- return null;
- }
- final Pattern toMatch = Pattern
- .compile(dirToProcess.replaceAll("\\\\\\\\", "\\\\"));
- File[] matches = directory.listFiles(new FilenameFilter() {
- public boolean accept(File arg0, String arg1) {
- return toMatch.matcher(arg1).matches();
- }
- });
- if (matches == null || matches.length == 0)
- return null;
- directory = matches[0];
- if (!directory.exists())
- return null;
- } // End process directory information
- }
- // Last step check if the given file can be found in this directory
- String filenameToLookFor = expandBrackets(fileParts[fileParts.length - 1], entry, database);
- final Pattern toMatch = Pattern.compile("^"
- + filenameToLookFor.replaceAll("\\\\\\\\", "\\\\") + "$");
- File[] matches = directory.listFiles(new FilenameFilter() {
- public boolean accept(File arg0, String arg1) {
- return toMatch.matcher(arg1).matches();
- }
- });
- if (matches == null || matches.length == 0)
- return null;
- try {
- return matches[0].getCanonicalPath();
- } catch (IOException e) {
- return null;
- }
- }
- static Pattern squareBracketsPattern = Pattern.compile("\\[.*?\\]");
- /**
- * Takes a string that contains bracketed expression and expands each of
- * these using ge…
Large files files are truncated, but you can click here to view the full file