PageRenderTime 28ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/src/main/java/net/sf/jabref/Util.java

https://github.com/edukempf/jabref
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

  1. /* Copyright (C) 2003-2012 JabRef contributors.
  2. This program is free software; you can redistribute it and/or modify
  3. it under the terms of the GNU General Public License as published by
  4. the Free Software Foundation; either version 2 of the License, or
  5. (at your option) any later version.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU General Public License for more details.
  10. You should have received a copy of the GNU General Public License along
  11. with this program; if not, write to the Free Software Foundation, Inc.,
  12. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  13. */
  14. // created by : Morten O. Alver 2003
  15. package net.sf.jabref;
  16. import java.awt.BorderLayout;
  17. import java.awt.CardLayout;
  18. import java.awt.Color;
  19. import java.awt.Component;
  20. import java.awt.Desktop;
  21. import java.awt.Dimension;
  22. import java.awt.Font;
  23. import java.awt.event.ActionEvent;
  24. import java.awt.event.ActionListener;
  25. import java.io.*;
  26. import java.net.MalformedURLException;
  27. import java.net.URI;
  28. import java.net.URISyntaxException;
  29. import java.net.URL;
  30. import java.net.URLConnection;
  31. import java.net.URLDecoder;
  32. import java.nio.charset.Charset;
  33. import java.nio.charset.CharsetEncoder;
  34. import java.text.NumberFormat;
  35. import java.text.SimpleDateFormat;
  36. import java.util.ArrayList;
  37. import java.util.Arrays;
  38. import java.util.Calendar;
  39. import java.util.Collection;
  40. import java.util.Collections;
  41. import java.util.Date;
  42. import java.util.HashMap;
  43. import java.util.HashSet;
  44. import java.util.LinkedList;
  45. import java.util.List;
  46. import java.util.Map;
  47. import java.util.Set;
  48. import java.util.StringTokenizer;
  49. import java.util.TreeSet;
  50. import java.util.Vector;
  51. import java.util.regex.Matcher;
  52. import java.util.regex.Pattern;
  53. import javax.swing.Action;
  54. import javax.swing.ActionMap;
  55. import javax.swing.BorderFactory;
  56. import javax.swing.Box;
  57. import javax.swing.InputMap;
  58. import javax.swing.JButton;
  59. import javax.swing.JComponent;
  60. import javax.swing.JDialog;
  61. import javax.swing.JFrame;
  62. import javax.swing.JLabel;
  63. import javax.swing.JOptionPane;
  64. import javax.swing.JPanel;
  65. import javax.swing.JProgressBar;
  66. import javax.swing.JRootPane;
  67. import javax.swing.JScrollPane;
  68. import javax.swing.JTextArea;
  69. import javax.swing.SwingUtilities;
  70. import javax.swing.undo.AbstractUndoableEdit;
  71. import javax.swing.undo.UndoableEdit;
  72. import net.sf.jabref.autocompleter.AbstractAutoCompleter;
  73. import net.sf.jabref.export.SaveSession;
  74. import net.sf.jabref.export.layout.Layout;
  75. import net.sf.jabref.export.layout.LayoutHelper;
  76. import net.sf.jabref.external.ExternalFileType;
  77. import net.sf.jabref.external.ExternalFileTypeEntryEditor;
  78. import net.sf.jabref.external.RegExpFileSearch;
  79. import net.sf.jabref.external.UnknownExternalFileType;
  80. import net.sf.jabref.groups.AbstractGroup;
  81. import net.sf.jabref.groups.KeywordGroup;
  82. import net.sf.jabref.gui.FileListEntry;
  83. import net.sf.jabref.gui.FileListEntryEditor;
  84. import net.sf.jabref.gui.FileListTableModel;
  85. import net.sf.jabref.labelPattern.LabelPatternUtil;
  86. import net.sf.jabref.net.URLDownload;
  87. import net.sf.jabref.undo.NamedCompound;
  88. import net.sf.jabref.undo.UndoableFieldChange;
  89. import net.sf.jabref.util.FileNameCleaner;
  90. import com.jgoodies.forms.builder.DefaultFormBuilder;
  91. import com.jgoodies.forms.layout.FormLayout;
  92. /**
  93. * utility functions
  94. */
  95. public class Util {
  96. /**
  97. * A static Object for date formatting. Please do not create the object
  98. * here, because there are some references from the Globals class.....
  99. *
  100. */
  101. private static SimpleDateFormat dateFormatter = null;
  102. /*
  103. * Colors are defined here.
  104. *
  105. */
  106. public static Color fieldsCol = new Color(180, 180, 200);
  107. /*
  108. * Integer values for indicating result of duplicate check (for entries):
  109. *
  110. */
  111. final static int TYPE_MISMATCH = -1, NOT_EQUAL = 0, EQUAL = 1, EMPTY_IN_ONE = 2,
  112. EMPTY_IN_TWO = 3, EMPTY_IN_BOTH = 4;
  113. final static NumberFormat idFormat;
  114. public static Pattern remoteLinkPattern = Pattern.compile("[a-z]+://.*");
  115. public static int MARK_COLOR_LEVELS = 6,
  116. MAX_MARKING_LEVEL = MARK_COLOR_LEVELS-1,
  117. IMPORT_MARK_LEVEL = MARK_COLOR_LEVELS;
  118. public static Pattern markNumberPattern = Pattern.compile(JabRefPreferences.getInstance().MARKING_WITH_NUMBER_PATTERN);
  119. static {
  120. idFormat = NumberFormat.getInstance();
  121. idFormat.setMinimumIntegerDigits(8);
  122. idFormat.setGroupingUsed(false);
  123. }
  124. public static int getMinimumIntegerDigits(){
  125. return idFormat.getMinimumIntegerDigits();
  126. }
  127. public static void pr(String s) {
  128. Globals.logger(s);
  129. }
  130. public static String nCase(String s) {
  131. // Make first character of String uppercase, and the
  132. // rest lowercase.
  133. if (s.length() > 1)
  134. return s.substring(0, 1).toUpperCase() + s.substring(1, s.length()).toLowerCase();
  135. else
  136. return s.toUpperCase();
  137. }
  138. public static String checkName(String s) {
  139. // Append '.bib' to the string unless it ends with that.
  140. if (s.length() < 4 || !s.substring(s.length() - 4).equalsIgnoreCase(".bib")) {
  141. return s + ".bib";
  142. }
  143. return s;
  144. }
  145. private static int idCounter = 0;
  146. public synchronized static String createNeutralId() {
  147. return idFormat.format(idCounter++);
  148. }
  149. /**
  150. * This method sets the location of a Dialog such that it is centered with
  151. * regard to another window, but not outside the screen on the left and the
  152. * top.
  153. */
  154. public static void placeDialog(java.awt.Dialog diag, java.awt.Container win) {
  155. diag.setLocationRelativeTo(win);
  156. }
  157. /**
  158. * This method translates a field or string from Bibtex notation, with
  159. * possibly text contained in " " or { }, and string references,
  160. * concatenated by '#' characters, into Bibkeeper notation, where string
  161. * references are enclosed in a pair of '#' characters.
  162. */
  163. public static String parseField(String content) {
  164. if (content.length() == 0)
  165. return content;
  166. String[] strings = content.split("#");
  167. StringBuffer result = new StringBuffer();
  168. for (String string : strings) {
  169. String s = string.trim();
  170. if (s.length() > 0) {
  171. char c = s.charAt(0);
  172. // String reference or not?
  173. if (c == '{' || c == '"') {
  174. result.append(shaveString(string));
  175. } else {
  176. // This part should normally be a string reference, but if it's
  177. // a pure number, it is not.
  178. String s2 = shaveString(s);
  179. try {
  180. Integer.parseInt(s2);
  181. // If there's no exception, it's a number.
  182. result.append(s2);
  183. } catch (NumberFormatException ex) {
  184. // otherwise append with hashes...
  185. result.append("#").append(s2).append("#");
  186. }
  187. }
  188. }
  189. }
  190. return result.toString();
  191. }
  192. /**
  193. * Will return the publication date of the given bibtex entry in conformance
  194. * to ISO 8601, i.e. either YYYY or YYYY-MM.
  195. *
  196. * @param entry
  197. * @return will return the publication date of the entry or null if no year
  198. * was found.
  199. */
  200. public static String getPublicationDate(BibtexEntry entry) {
  201. Object o = entry.getField("year");
  202. if (o == null)
  203. return null;
  204. String year = toFourDigitYear(o.toString());
  205. o = entry.getField("month");
  206. if (o != null) {
  207. int month = Util.getMonthNumber(o.toString());
  208. if (month != -1) {
  209. return year + "-" + (month + 1 < 10 ? "0" : "") + (month + 1);
  210. }
  211. }
  212. return year;
  213. }
  214. public static String shaveString(String s) {
  215. // returns the string, after shaving off whitespace at the beginning
  216. // and end, and removing (at most) one pair of braces or " surrounding
  217. // it.
  218. if (s == null)
  219. return null;
  220. char ch, ch2;
  221. int beg = 0, end = s.length();
  222. // We start out assuming nothing will be removed.
  223. boolean begok = false, endok = false;
  224. while (!begok) {
  225. if (beg < s.length()) {
  226. ch = s.charAt(beg);
  227. if (Character.isWhitespace(ch))
  228. beg++;
  229. else
  230. begok = true;
  231. } else
  232. begok = true;
  233. }
  234. while (!endok) {
  235. if (end > beg + 1) {
  236. ch = s.charAt(end - 1);
  237. if (Character.isWhitespace(ch))
  238. end--;
  239. else
  240. endok = true;
  241. } else
  242. endok = true;
  243. }
  244. if (end > beg + 1) {
  245. ch = s.charAt(beg);
  246. ch2 = s.charAt(end - 1);
  247. if (((ch == '{') && (ch2 == '}')) || ((ch == '"') && (ch2 == '"'))) {
  248. beg++;
  249. end--;
  250. }
  251. }
  252. s = s.substring(beg, end);
  253. return s;
  254. }
  255. public static String rtrim(String s) {
  256. return s.replaceAll("\\s+$","");
  257. }
  258. /**
  259. * This method returns a String similar to the one passed in, except that it
  260. * is molded into a form that is acceptable for bibtex.
  261. *
  262. * Watch-out that the returned string might be of length 0 afterwards.
  263. *
  264. * @param key
  265. * mayBeNull
  266. */
  267. public static String checkLegalKey(String key) {
  268. if (key == null)
  269. return null;
  270. if (!JabRefPreferences.getInstance().getBoolean("enforceLegalBibtexKey")) {
  271. // User doesn't want us to enforce legal characters. We must still look
  272. // for whitespace and some characters such as commas, since these would
  273. // interfere with parsing:
  274. StringBuilder newKey = new StringBuilder();
  275. for (int i = 0; i < key.length(); i++) {
  276. char c = key.charAt(i);
  277. if (!Character.isWhitespace(c) && (c != '{') && (c != '\\') && (c != '"')
  278. && (c != '}') && (c != ','))
  279. newKey.append(c);
  280. }
  281. return newKey.toString();
  282. }
  283. StringBuilder newKey = new StringBuilder();
  284. for (int i = 0; i < key.length(); i++) {
  285. char c = key.charAt(i);
  286. if (!Character.isWhitespace(c) && (c != '#') && (c != '{') && (c != '\\') && (c != '"')
  287. && (c != '}') && (c != '~') && (c != ',') && (c != '^') && (c != '\''))
  288. newKey.append(c);
  289. }
  290. // Replace non-english characters like umlauts etc. with a sensible
  291. // letter or letter combination that bibtex can accept.
  292. String newKeyS = replaceSpecialCharacters(newKey.toString());
  293. return newKeyS;
  294. }
  295. /**
  296. * Replace non-english characters like umlauts etc. with a sensible letter
  297. * or letter combination that bibtex can accept. The basis for replacement
  298. * is the HashMap GLobals.UNICODE_CHARS.
  299. */
  300. public static String replaceSpecialCharacters(String s) {
  301. for (Map.Entry<String, String> chrAndReplace : Globals.UNICODE_CHARS.entrySet()){
  302. s = s.replaceAll(chrAndReplace.getKey(), chrAndReplace.getValue());
  303. }
  304. return s;
  305. }
  306. static public String _wrap2(String in, int wrapAmount) {
  307. // The following line cuts out all whitespace and replaces them with
  308. // single
  309. // spaces:
  310. // in = in.replaceAll("[ ]+"," ").replaceAll("[\\t]+"," ");
  311. // StringBuffer out = new StringBuffer(in);
  312. StringBuffer out = new StringBuffer(in.replaceAll("[ \\t\\r]+", " "));
  313. int p = in.length() - wrapAmount;
  314. int lastInserted = -1;
  315. while (p > 0) {
  316. p = out.lastIndexOf(" ", p);
  317. if (p <= 0 || p <= 20)
  318. break;
  319. int lbreak = out.indexOf("\n", p);
  320. System.out.println(lbreak + " " + lastInserted);
  321. if ((lbreak > p) && ((lastInserted >= 0) && (lbreak < lastInserted))) {
  322. p = lbreak - wrapAmount;
  323. } else {
  324. out.insert(p, "\n\t");
  325. lastInserted = p;
  326. p -= wrapAmount;
  327. }
  328. }
  329. return out.toString();
  330. }
  331. static public String wrap2(String in, int wrapAmount) {
  332. return net.sf.jabref.imports.FieldContentParser.wrap(in, wrapAmount);
  333. }
  334. static public String __wrap2(String in, int wrapAmount) {
  335. // The following line cuts out all whitespace except line breaks, and
  336. // replaces
  337. // with single spaces. Line breaks are padded with a tab character:
  338. StringBuffer out = new StringBuffer(in.replaceAll("[ \\t\\r]+", " "));
  339. int p = 0;
  340. // int lastInserted = -1;
  341. while (p < out.length()) {
  342. int q = out.indexOf(" ", p + wrapAmount);
  343. if ((q < 0) || (q >= out.length()))
  344. break;
  345. int lbreak = out.indexOf("\n", p);
  346. // System.out.println(lbreak);
  347. if ((lbreak > p) && (lbreak < q)) {
  348. p = lbreak + 1;
  349. int piv = lbreak + 1;
  350. if ((out.length() > piv) && !(out.charAt(piv) == '\t'))
  351. out.insert(piv, "\n\t");
  352. } else {
  353. // System.out.println(q+" "+out.length());
  354. out.deleteCharAt(q);
  355. out.insert(q, "\n\t");
  356. p = q + 1;
  357. }
  358. }
  359. return out.toString();// .replaceAll("\n", "\n\t");
  360. }
  361. public static TreeSet<String> findDeliminatedWordsInField(BibtexDatabase db, String field,
  362. String deliminator) {
  363. TreeSet<String> res = new TreeSet<String>();
  364. for (String s : db.getKeySet()){
  365. BibtexEntry be = db.getEntryById(s);
  366. Object o = be.getField(field);
  367. if (o != null) {
  368. String fieldValue = o.toString().trim();
  369. StringTokenizer tok = new StringTokenizer(fieldValue, deliminator);
  370. while (tok.hasMoreTokens())
  371. res.add(nCase(tok.nextToken().trim()));
  372. }
  373. }
  374. return res;
  375. }
  376. /**
  377. * Returns a HashMap containing all words used in the database in the given
  378. * field type. Characters in <code>remove</code> are not included.
  379. *
  380. * @param db
  381. * a <code>BibtexDatabase</code> value
  382. * @param field
  383. * a <code>String</code> value
  384. * @param remove
  385. * a <code>String</code> value
  386. * @return a <code>HashSet</code> value
  387. */
  388. public static TreeSet<String> findAllWordsInField(BibtexDatabase db, String field, String remove) {
  389. TreeSet<String> res = new TreeSet<String>();
  390. StringTokenizer tok;
  391. for (String s : db.getKeySet()){
  392. BibtexEntry be = db.getEntryById(s);
  393. Object o = be.getField(field);
  394. if (o != null) {
  395. tok = new StringTokenizer(o.toString(), remove, false);
  396. while (tok.hasMoreTokens())
  397. res.add(nCase(tok.nextToken().trim()));
  398. }
  399. }
  400. return res;
  401. }
  402. /**
  403. * Finds all authors' last names in all the given fields for the given database.
  404. * @param db The database.
  405. * @param fields The fields to look in.
  406. * @return a set containing the names.
  407. */
  408. public static Set<String> findAuthorLastNames(BibtexDatabase db, List<String> fields) {
  409. Set<String> res = new TreeSet<String>();
  410. for (String s : db.getKeySet()){
  411. BibtexEntry be = db.getEntryById(s);
  412. for (String field : fields) {
  413. String val = be.getField(field);
  414. if ((val != null) && (val.length() > 0)) {
  415. AuthorList al = AuthorList.getAuthorList(val);
  416. for (int i=0; i<al.size(); i++) {
  417. AuthorList.Author a = al.getAuthor(i);
  418. String lastName = a.getLast();
  419. if ((lastName != null) && (lastName.length() > 0))
  420. res.add(lastName);
  421. }
  422. }
  423. }
  424. }
  425. return res;
  426. }
  427. /**
  428. * Takes a String array and returns a string with the array's elements
  429. * delimited by a certain String.
  430. *
  431. * @param strs
  432. * String array to convert.
  433. * @param delimiter
  434. * String to use as delimiter.
  435. * @return Delimited String.
  436. */
  437. public static String stringArrayToDelimited(String[] strs, String delimiter) {
  438. if ((strs == null) || (strs.length == 0))
  439. return "";
  440. if (strs.length == 1)
  441. return strs[0];
  442. StringBuffer sb = new StringBuffer();
  443. for (int i = 0; i < strs.length - 1; i++) {
  444. sb.append(strs[i]);
  445. sb.append(delimiter);
  446. }
  447. sb.append(strs[strs.length - 1]);
  448. return sb.toString();
  449. }
  450. /**
  451. * Takes a delimited string, splits it and returns
  452. *
  453. * @param names
  454. * a <code>String</code> value
  455. * @return a <code>String[]</code> value
  456. */
  457. public static String[] delimToStringArray(String names, String delimiter) {
  458. if (names == null)
  459. return null;
  460. return names.split(delimiter);
  461. }
  462. /**
  463. * Creates a substring from a text
  464. *
  465. * @param text
  466. * @param i
  467. * @param terminateOnEndBraceOnly
  468. * @return
  469. */
  470. public static String getPart(String text, int i, boolean terminateOnEndBraceOnly) {
  471. char c;
  472. int count = 0;
  473. StringBuffer part = new StringBuffer();
  474. // advance to first char and skip whitespace
  475. i++;
  476. while (i < text.length() && Character.isWhitespace(text.charAt(i))){
  477. i++;
  478. }
  479. // then grab whathever is the first token (counting braces)
  480. while (i < text.length()){
  481. c = text.charAt(i);
  482. if (!terminateOnEndBraceOnly && count == 0 && Character.isWhitespace(c)) {
  483. i--; // end argument and leave whitespace for further
  484. // processing
  485. break;
  486. }
  487. if (c == '}' && --count < 0)
  488. break;
  489. else if (c == '{')
  490. count++;
  491. part.append(c);
  492. i++;
  493. }
  494. return part.toString();
  495. }
  496. /**
  497. * Open a http/pdf/ps viewer for the given link string.
  498. */
  499. public static void openExternalViewer(MetaData metaData, String link, String fieldName)
  500. throws IOException {
  501. if (fieldName.equals("ps") || fieldName.equals("pdf")) {
  502. // Find the default directory for this field type:
  503. String[] dir = metaData.getFileDirectory(fieldName);
  504. File file = expandFilename(link, dir);
  505. // Check that the file exists:
  506. if ((file == null) || !file.exists()) {
  507. throw new IOException(Globals.lang("File not found") + " (" + fieldName + "): '"
  508. + link + "'.");
  509. }
  510. link = file.getCanonicalPath();
  511. // Use the correct viewer even if pdf and ps are mixed up:
  512. String[] split = file.getName().split("\\.");
  513. if (split.length >= 2) {
  514. if (split[split.length - 1].equalsIgnoreCase("pdf"))
  515. fieldName = "pdf";
  516. else if (split[split.length - 1].equalsIgnoreCase("ps")
  517. || (split.length >= 3 && split[split.length - 2].equalsIgnoreCase("ps")))
  518. fieldName = "ps";
  519. }
  520. } else if (fieldName.equals("doi")) {
  521. fieldName = "url";
  522. // sanitizing is done below at the treatment of "URL"
  523. // in sanatizeUrl a doi-link is correctly treated
  524. } else if (fieldName.equals("eprint")) {
  525. fieldName = "url";
  526. link = sanitizeUrl(link);
  527. // Check to see if link field already contains a well formated URL
  528. if (!link.startsWith("http://")) {
  529. link = Globals.ARXIV_LOOKUP_PREFIX + link;
  530. }
  531. }
  532. if (fieldName.equals("url")) { // html
  533. try {
  534. link = sanitizeUrl(link);
  535. ExternalFileType fileType = Globals.prefs.getExternalFileTypeByExt("html");
  536. openExternalFilePlatformIndependent(fileType, link);
  537. } catch (IOException e) {
  538. System.err.println(Globals.lang("Error_opening_file_'%0'.", link));
  539. e.printStackTrace();
  540. }
  541. } else if (fieldName.equals("ps")) {
  542. try {
  543. if (Globals.ON_MAC) {
  544. ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("ps");
  545. String viewer = type != null ? type.getOpenWith() : Globals.prefs.get("psviewer");
  546. String[] cmd = { "/usr/bin/open", "-a", viewer, link };
  547. Runtime.getRuntime().exec(cmd);
  548. } else if (Globals.ON_WIN) {
  549. openFileOnWindows(link, true);
  550. /*
  551. * cmdArray[0] = Globals.prefs.get("psviewer"); cmdArray[1] =
  552. * link; Process child = Runtime.getRuntime().exec(
  553. * cmdArray[0] + " " + cmdArray[1]);
  554. */
  555. } else {
  556. ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("ps");
  557. String viewer = type != null ? type.getOpenWith() : "xdg-open";
  558. String[] cmdArray = new String[2];
  559. cmdArray[0] = viewer;
  560. cmdArray[1] = link;
  561. Runtime.getRuntime().exec(cmdArray);
  562. }
  563. } catch (IOException e) {
  564. System.err.println("An error occured on the command: "
  565. + Globals.prefs.get("psviewer") + " " + link);
  566. }
  567. } else if (fieldName.equals("pdf")) {
  568. try {
  569. if (Globals.ON_MAC) {
  570. ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("pdf");
  571. String viewer = type != null ? type.getOpenWith() : Globals.prefs.get("psviewer");
  572. String[] cmd = { "/usr/bin/open", "-a", viewer, link };
  573. Runtime.getRuntime().exec(cmd);
  574. } else if (Globals.ON_WIN) {
  575. openFileOnWindows(link, true);
  576. /*
  577. * String[] spl = link.split("\\\\"); StringBuffer sb = new
  578. * StringBuffer(); for (int i = 0; i < spl.length; i++) { if
  579. * (i > 0) sb.append("\\"); if (spl[i].indexOf(" ") >= 0)
  580. * spl[i] = "\"" + spl[i] + "\""; sb.append(spl[i]); }
  581. * //pr(sb.toString()); link = sb.toString();
  582. *
  583. * String cmd = "cmd.exe /c start " + link;
  584. *
  585. * Process child = Runtime.getRuntime().exec(cmd);
  586. */
  587. } else {
  588. ExternalFileType type = Globals.prefs.getExternalFileTypeByExt("pdf");
  589. String viewer = type != null ? type.getOpenWith() : Globals.prefs.get("psviewer");
  590. String[] cmdArray = new String[2];
  591. cmdArray[0] = viewer;
  592. cmdArray[1] = link;
  593. // Process child = Runtime.getRuntime().exec(cmdArray[0]+"
  594. // "+cmdArray[1]);
  595. Runtime.getRuntime().exec(cmdArray);
  596. }
  597. } catch (IOException e) {
  598. e.printStackTrace();
  599. System.err.println("An error occured on the command: "
  600. + Globals.prefs.get("pdfviewer") + " #" + link);
  601. System.err.println(e.getMessage());
  602. }
  603. } else {
  604. System.err
  605. .println("Message: currently only PDF, PS and HTML files can be opened by double clicking");
  606. }
  607. }
  608. /**
  609. * Opens a file on a Windows system, using its default viewer.
  610. *
  611. * @param link
  612. * The file name.
  613. * @param localFile
  614. * true if it is a local file, not an URL.
  615. * @throws IOException
  616. */
  617. public static void openFileOnWindows(String link, boolean localFile) throws IOException {
  618. /*
  619. * if (localFile) { String[] spl = link.split("\\\\"); StringBuffer sb =
  620. * new StringBuffer(); for (int i = 0; i < spl.length; i++) { if (i > 0)
  621. * sb.append("\\"); if (spl[i].indexOf(" ") >= 0) spl[i] = "\"" + spl[i] +
  622. * "\""; sb.append(spl[i]); } link = sb.toString(); }
  623. */
  624. link = link.replaceAll("&", "\"&\"").replaceAll(" ", "\" \"");
  625. // Bug fix for:
  626. // http://sourceforge.net/tracker/index.php?func=detail&aid=1489454&group_id=92314&atid=600306
  627. String cmd;
  628. if (Globals.osName.startsWith("Windows 9")) {
  629. cmd = "command.com /c start " + link;
  630. } else {
  631. cmd = "cmd.exe /c start " + link;
  632. }
  633. Runtime.getRuntime().exec(cmd);
  634. }
  635. /**
  636. * Opens a file on a Windows system, using the given application.
  637. *
  638. * @param link The file name.
  639. * @param application Link to the app that opens the file.
  640. * @throws IOException
  641. */
  642. public static void openFileWithApplicationOnWindows(String link, String application)
  643. throws IOException {
  644. link = link.replaceAll("&", "\"&\"").replaceAll(" ", "\" \"");
  645. Runtime.getRuntime().exec(application + " " + link);
  646. }
  647. /**
  648. * Open an external file, attempting to use the correct viewer for it.
  649. *
  650. * @param metaData
  651. * The MetaData for the database this file belongs to.
  652. * @param link
  653. * The file name.
  654. * @return false if the link couldn't be resolved, true otherwise.
  655. */
  656. public static boolean openExternalFileAnyFormat(final MetaData metaData, String link,
  657. final ExternalFileType fileType) throws IOException {
  658. boolean httpLink = false;
  659. if (remoteLinkPattern.matcher(link.toLowerCase()).matches()) {
  660. httpLink = true;
  661. }
  662. /*if (link.toLowerCase().startsWith("file://")) {
  663. link = link.substring(7);
  664. }
  665. final String ln = link;
  666. if (remoteLinkPattern.matcher(link.toLowerCase()).matches()) {
  667. (new Thread(new Runnable() {
  668. public void run() {
  669. openRemoteExternalFile(metaData, ln, fileType);
  670. }
  671. })).start();
  672. return true;
  673. }*/
  674. //boolean httpLink = link.toLowerCase().startsWith("http:")
  675. // || link.toLowerCase().startsWith("ftp:");
  676. // For other platforms we'll try to find the file type:
  677. File file = new File(link);
  678. if (!httpLink) {
  679. File tmp = expandFilename(metaData, link);
  680. if (tmp != null)
  681. file = tmp;
  682. }
  683. // Check if we have arrived at a file type, and either an http link or an existing file:
  684. if ((httpLink || file.exists()) && (fileType != null)) {
  685. // Open the file:
  686. String filePath = httpLink ? link : file.getPath();
  687. openExternalFilePlatformIndependent(fileType, filePath);
  688. return true;
  689. } else {
  690. return false;
  691. // No file matched the name, or we didn't know the file type.
  692. }
  693. }
  694. private static void openExternalFilePlatformIndependent(ExternalFileType fileType, String filePath) throws IOException {
  695. if (Globals.ON_MAC) {
  696. // Use "-a <application>" if the app is specified, and just "open <filename>" otherwise:
  697. String[] cmd = ((fileType.getOpenWith() != null) && (fileType.getOpenWith().length() > 0)) ?
  698. new String[] { "/usr/bin/open", "-a", fileType.getOpenWith(), filePath } :
  699. new String[] { "/usr/bin/open", filePath };
  700. Runtime.getRuntime().exec(cmd);
  701. } else if (Globals.ON_WIN) {
  702. if ((fileType.getOpenWith() != null) && (fileType.getOpenWith().length() > 0)) {
  703. // Application is specified. Use it:
  704. openFileWithApplicationOnWindows(filePath, fileType.getOpenWith());
  705. } else
  706. openFileOnWindows(filePath, true);
  707. } else {
  708. // Use the given app if specified, and the universal "xdg-open" otherwise:
  709. String[] openWith;
  710. if ((fileType.getOpenWith() != null) && (fileType.getOpenWith().length() > 0))
  711. openWith = fileType.getOpenWith().split(" ");
  712. else
  713. openWith = new String[] {"xdg-open"};
  714. String[] cmdArray = new String[openWith.length+1];
  715. System.arraycopy(openWith, 0, cmdArray, 0, openWith.length);
  716. cmdArray[cmdArray.length-1] = filePath;
  717. Runtime.getRuntime().exec(cmdArray);
  718. }
  719. }
  720. public static void openRemoteExternalFile(final MetaData metaData,
  721. final String link, final ExternalFileType fileType) {
  722. File temp = null;
  723. try {
  724. temp = File.createTempFile("jabref-link", "."+fileType.getExtension());
  725. temp.deleteOnExit();
  726. System.out.println("Downloading to '"+temp.getPath()+"'");
  727. URLDownload ud = new URLDownload(null, new URL(link), temp);
  728. ud.download();
  729. System.out.println("Done");
  730. } catch (MalformedURLException ex) {
  731. ex.printStackTrace();
  732. } catch (IOException ex) {
  733. ex.printStackTrace();
  734. }
  735. final String ln = temp.getPath();
  736. SwingUtilities.invokeLater(new Runnable() {
  737. public void run() {
  738. try {
  739. openExternalFileAnyFormat(metaData, ln, fileType);
  740. } catch (IOException ex) {
  741. ex.printStackTrace();
  742. }
  743. }
  744. });
  745. }
  746. public static boolean openExternalFileUnknown(JabRefFrame frame, BibtexEntry entry, MetaData metaData,
  747. String link, UnknownExternalFileType fileType) throws IOException {
  748. String cancelMessage = Globals.lang("Unable to open file.");
  749. String[] options = new String[] {Globals.lang("Define '%0'", fileType.getName()),
  750. Globals.lang("Change file type"), Globals.lang("Cancel")};
  751. String defOption = options[0];
  752. int answer = JOptionPane.showOptionDialog(frame, Globals.lang("This external link is of the type '%0', which is undefined. What do you want to do?",
  753. fileType.getName()),
  754. Globals.lang("Undefined file type"), JOptionPane.YES_NO_CANCEL_OPTION,
  755. JOptionPane.QUESTION_MESSAGE, null, options, defOption);
  756. if (answer == JOptionPane.CANCEL_OPTION) {
  757. frame.output(cancelMessage);
  758. return false;
  759. }
  760. else if (answer == JOptionPane.YES_OPTION) {
  761. // User wants to define the new file type. Show the dialog:
  762. ExternalFileType newType = new ExternalFileType(fileType.getName(), "", "", "", "new");
  763. ExternalFileTypeEntryEditor editor = new ExternalFileTypeEntryEditor(frame, newType);
  764. editor.setVisible(true);
  765. if (editor.okPressed()) {
  766. // Get the old list of types, add this one, and update the list in prefs:
  767. List<ExternalFileType> fileTypes = new ArrayList<ExternalFileType>();
  768. ExternalFileType[] oldTypes = Globals.prefs.getExternalFileTypeSelection();
  769. Collections.addAll(fileTypes, oldTypes);
  770. fileTypes.add(newType);
  771. Collections.sort(fileTypes);
  772. Globals.prefs.setExternalFileTypes(fileTypes);
  773. // Finally, open the file:
  774. return openExternalFileAnyFormat(metaData, link, newType);
  775. } else {
  776. // Cancelled:
  777. frame.output(cancelMessage);
  778. return false;
  779. }
  780. }
  781. else {
  782. // User wants to change the type of this link.
  783. // First get a model of all file links for this entry:
  784. FileListTableModel tModel = new FileListTableModel();
  785. String oldValue = entry.getField(GUIGlobals.FILE_FIELD);
  786. tModel.setContent(oldValue);
  787. FileListEntry flEntry = null;
  788. // Then find which one we are looking at:
  789. for (int i=0; i<tModel.getRowCount(); i++) {
  790. FileListEntry iEntry = tModel.getEntry(i);
  791. if (iEntry.getLink().equals(link)) {
  792. flEntry = iEntry;
  793. break;
  794. }
  795. }
  796. if (flEntry == null) {
  797. // This shouldn't happen, so I'm not sure what to put in here:
  798. throw new RuntimeException("Could not find the file list entry "+link+" in "+entry.toString());
  799. }
  800. FileListEntryEditor editor = new FileListEntryEditor(frame, flEntry, false, true, metaData);
  801. editor.setVisible(true, false);
  802. if (editor.okPressed()) {
  803. // Store the changes and add an undo edit:
  804. String newValue = tModel.getStringRepresentation();
  805. UndoableFieldChange ce = new UndoableFieldChange(entry, GUIGlobals.FILE_FIELD,
  806. oldValue, newValue);
  807. entry.setField(GUIGlobals.FILE_FIELD, newValue);
  808. frame.basePanel().undoManager.addEdit(ce);
  809. frame.basePanel().markBaseChanged();
  810. // Finally, open the link:
  811. return openExternalFileAnyFormat(metaData, flEntry.getLink(), flEntry.getType());
  812. } else {
  813. // Cancelled:
  814. frame.output(cancelMessage);
  815. return false;
  816. }
  817. }
  818. }
  819. /**
  820. * Make sure an URL is "portable", in that it doesn't contain bad characters
  821. * that break the open command in some OSes.
  822. *
  823. * A call to this method will also remove \\url{} enclosings and clean DOI links.
  824. *
  825. * @param link :the URL to sanitize.
  826. * @return Sanitized URL
  827. */
  828. public static String sanitizeUrl(String link) {
  829. link = link.trim();
  830. // First check if it is enclosed in \\url{}. If so, remove
  831. // the wrapper.
  832. if (link.startsWith("\\url{") && link.endsWith("}"))
  833. link = link.substring(5, link.length() - 1);
  834. if (link.matches("^doi:/*.*")){
  835. // Remove 'doi:'
  836. link = link.replaceFirst("^doi:/*", "");
  837. link = Globals.DOI_LOOKUP_PREFIX + link;
  838. }
  839. // converts doi-only link to full http address
  840. // Morten Alver 6 Nov 2012: this extracts a nonfunctional DOI from some complete
  841. // http addresses (e.g. http://onlinelibrary.wiley.com/doi/10.1002/rra.999/abstract, where
  842. // the trailing "/abstract" is included but doesn't lead to a resolvable DOI).
  843. // To prevent mangling of working URLs I'm disabling this check if the link is already
  844. // a full http link:
  845. if (checkForPlainDOI(link) && !link.startsWith("http://")) {
  846. link = Globals.DOI_LOOKUP_PREFIX + getDOI(link);
  847. }
  848. link = link.replaceAll("\\+", "%2B");
  849. try {
  850. link = URLDecoder.decode(link, "UTF-8");
  851. } catch (UnsupportedEncodingException ignored) {
  852. }
  853. /**
  854. * Fix for: [ 1574773 ] sanitizeUrl() breaks ftp:// and file:///
  855. *
  856. * http://sourceforge.net/tracker/index.php?func=detail&aid=1574773&group_id=92314&atid=600306
  857. */
  858. try {
  859. return new URI(null, link, null).toASCIIString();
  860. } catch (URISyntaxException e) {
  861. return link;
  862. }
  863. }
  864. /**
  865. * Searches the given directory and subdirectories for a pdf file with name
  866. * as given + ".pdf"
  867. */
  868. public static String findPdf(String key, String extension, String directory, OpenFileFilter off) {
  869. // String filename = key + "."+extension;
  870. /*
  871. * Simon Fischer's patch for replacing a regexp in keys before
  872. * converting to filename:
  873. *
  874. * String regex = Globals.prefs.get("basenamePatternRegex"); if ((regex !=
  875. * null) && (regex.trim().length() > 0)) { String replacement =
  876. * Globals.prefs.get("basenamePatternReplacement"); key =
  877. * key.replaceAll(regex, replacement); }
  878. */
  879. if (!directory.endsWith(System.getProperty("file.separator")))
  880. directory += System.getProperty("file.separator");
  881. String found = findInDir(key, directory, off, 0);
  882. if (found != null)
  883. return found.substring(directory.length());
  884. else
  885. return null;
  886. }
  887. public static Map<BibtexEntry, List<File>> findAssociatedFiles(Collection<BibtexEntry> entries, Collection<String> extensions, Collection<File> directories){
  888. HashMap<BibtexEntry, List<File>> result = new HashMap<BibtexEntry, List<File>>();
  889. // First scan directories
  890. Set<File> filesWithExtension = findFiles(extensions, directories);
  891. // Initialize Result-Set
  892. for (BibtexEntry entry : entries){
  893. result.put(entry, new ArrayList<File>());
  894. }
  895. boolean exactOnly = Globals.prefs.getBoolean(JabRefPreferences.AUTOLINK_EXACT_KEY_ONLY);
  896. // Now look for keys
  897. nextFile:
  898. for (File file : filesWithExtension){
  899. String name = file.getName();
  900. int dot = name.lastIndexOf('.');
  901. // First, look for exact matches:
  902. for (BibtexEntry entry : entries){
  903. String citeKey = entry.getCiteKey();
  904. if ((citeKey != null) && (citeKey.length() > 0)) {
  905. if (dot > 0) {
  906. if (name.substring(0, dot).equals(citeKey)) {
  907. result.get(entry).add(file);
  908. continue nextFile;
  909. }
  910. }
  911. }
  912. }
  913. // If we get here, we didn't find any exact matches. If non-exact
  914. // matches are allowed, try to find one:
  915. if (!exactOnly) {
  916. for (BibtexEntry entry : entries){
  917. String citeKey = entry.getCiteKey();
  918. if ((citeKey != null) && (citeKey.length() > 0)) {
  919. if (name.startsWith(citeKey)){
  920. result.get(entry).add(file);
  921. continue nextFile;
  922. }
  923. }
  924. }
  925. }
  926. }
  927. return result;
  928. }
  929. public static Set<File> findFiles(Collection<String> extensions, Collection<File> directories) {
  930. Set<File> result = new HashSet<File>();
  931. for (File directory : directories){
  932. result.addAll(findFiles(extensions, directory));
  933. }
  934. return result;
  935. }
  936. private static Collection<? extends File> findFiles(Collection<String> extensions, File directory) {
  937. Set<File> result = new HashSet<File>();
  938. File[] children = directory.listFiles();
  939. if (children == null)
  940. return result; // No permission?
  941. for (File child : children){
  942. if (child.isDirectory()) {
  943. result.addAll(findFiles(extensions, child));
  944. } else {
  945. String extension = getFileExtension(child);
  946. if (extension != null){
  947. if (extensions.contains(extension)){
  948. result.add(child);
  949. }
  950. }
  951. }
  952. }
  953. return result;
  954. }
  955. /**
  956. * Returns the extension of a file or null if the file does not have one (no . in name).
  957. *
  958. * @param file
  959. *
  960. * @return The extension, trimmed and in lowercase.
  961. */
  962. public static String getFileExtension(File file) {
  963. String name = file.getName();
  964. int pos = name.lastIndexOf('.');
  965. String extension = ((pos >= 0) && (pos < name.length() - 1)) ? name.substring(pos + 1)
  966. .trim().toLowerCase() : null;
  967. return extension;
  968. }
  969. /**
  970. * New version of findPdf that uses findFiles.
  971. *
  972. * The search pattern will be read from the preferences.
  973. *
  974. * The [extension]-tags in this pattern will be replace by the given
  975. * extension parameter.
  976. *
  977. */
  978. public static String findPdf(BibtexEntry entry, String extension, String directory) {
  979. return findPdf(entry, extension, new String[] { directory });
  980. }
  981. /**
  982. * Convenience method for findPDF. Can search multiple PDF directories.
  983. */
  984. public static String findPdf(BibtexEntry entry, String extension, String[] directories) {
  985. String regularExpression;
  986. if (Globals.prefs.getBoolean(JabRefPreferences.USE_REG_EXP_SEARCH_KEY)) {
  987. regularExpression = Globals.prefs.get(JabRefPreferences.REG_EXP_SEARCH_EXPRESSION_KEY);
  988. } else {
  989. regularExpression = Globals.prefs
  990. .get(JabRefPreferences.DEFAULT_REG_EXP_SEARCH_EXPRESSION_KEY);
  991. }
  992. regularExpression = regularExpression.replaceAll("\\[extension\\]", extension);
  993. return findFile(entry, null, directories, regularExpression, true);
  994. }
  995. /**
  996. * Convenience menthod for findPDF. Searches for a file of the given type.
  997. * @param entry The BibtexEntry to search for a link for.
  998. * @param fileType The file type to search for.
  999. * @return The link to the file found, or null if not found.
  1000. */
  1001. public static String findFile(BibtexEntry entry, ExternalFileType fileType, List<String> extraDirs) {
  1002. List<String> dirs = new ArrayList<String>();
  1003. dirs.addAll(extraDirs);
  1004. if (Globals.prefs.hasKey(fileType.getExtension()+"Directory")) {
  1005. dirs.add(Globals.prefs.get(fileType.getExtension()+"Directory"));
  1006. }
  1007. String [] directories = dirs.toArray(new String[dirs.size()]);
  1008. return findPdf(entry, fileType.getExtension(), directories);
  1009. }
  1010. /**
  1011. * Searches the given directory and file name pattern for a file for the
  1012. * bibtexentry.
  1013. *
  1014. * Used to fix:
  1015. *
  1016. * http://sourceforge.net/tracker/index.php?func=detail&aid=1503410&group_id=92314&atid=600309
  1017. *
  1018. * Requirements:
  1019. * - Be able to find the associated PDF in a set of given directories.
  1020. * - Be able to return a relative path or absolute path.
  1021. * - Be fast.
  1022. * - Allow for flexible naming schemes in the PDFs.
  1023. *
  1024. * Syntax scheme for file:
  1025. * <ul>
  1026. * <li>* Any subDir</li>
  1027. * <li>** Any subDir (recursiv)</li>
  1028. * <li>[key] Key from bibtex file and database</li>
  1029. * <li>.* Anything else is taken to be a Regular expression.</li>
  1030. * </ul>
  1031. *
  1032. * @param entry
  1033. * non-null
  1034. * @param database
  1035. * non-null
  1036. * @param directory
  1037. * A set of root directories to start the search from. Paths are
  1038. * returned relative to these directories if relative is set to
  1039. * true. These directories will not be expanded or anything. Use
  1040. * the file attribute for this.
  1041. * @param file
  1042. * non-null
  1043. *
  1044. * @param relative
  1045. * whether to return relative file paths or absolute ones
  1046. *
  1047. * @return Will return the first file found to match the given criteria or
  1048. * null if none was found.
  1049. */
  1050. public static String findFile(BibtexEntry entry, BibtexDatabase database, String[] directory,
  1051. String file, boolean relative) {
  1052. for (String aDirectory : directory) {
  1053. String result = findFile(entry, database, aDirectory, file, relative);
  1054. if (result != null) {
  1055. return result;
  1056. }
  1057. }
  1058. return null;
  1059. }
  1060. /**
  1061. * Removes optional square brackets from the string s
  1062. *
  1063. * @param s
  1064. * @return
  1065. */
  1066. public static String stripBrackets(String s) {
  1067. int beginIndex = (s.startsWith("[") ? 1 : 0);
  1068. int endIndex = (s.endsWith("]") ? s.length() - 1 : s.length());
  1069. return s.substring(beginIndex, endIndex);
  1070. }
  1071. public static ArrayList<String[]> parseMethodsCalls(String calls) throws RuntimeException {
  1072. ArrayList<String[]> result = new ArrayList<String[]>();
  1073. char[] c = calls.toCharArray();
  1074. int i = 0;
  1075. while (i < c.length) {
  1076. int start = i;
  1077. if (Character.isJavaIdentifierStart(c[i])) {
  1078. i++;
  1079. while (i < c.length && (Character.isJavaIdentifierPart(c[i]) || c[i] == '.')) {
  1080. i++;
  1081. }
  1082. if (i < c.length && c[i] == '(') {
  1083. String method = calls.substring(start, i);
  1084. // Skip the brace
  1085. i++;
  1086. if (i < c.length){
  1087. if (c[i] == '"'){
  1088. // Parameter is in format "xxx"
  1089. // Skip "
  1090. i++;
  1091. int startParam = i;
  1092. i++;
  1093. boolean escaped = false;
  1094. while (i + 1 < c.length &&
  1095. !(!escaped && c[i] == '"' && c[i + 1] == ')')) {
  1096. if (c[i] == '\\') {
  1097. escaped = !escaped;
  1098. }
  1099. else
  1100. escaped = false;
  1101. i++;
  1102. }
  1103. String param = calls.substring(startParam, i);
  1104. result.add(new String[] { method, param });
  1105. } else {
  1106. // Parameter is in format xxx
  1107. int startParam = i;
  1108. while (i < c.length && c[i] != ')') {
  1109. i++;
  1110. }
  1111. String param = calls.substring(startParam, i);
  1112. result.add(new String[] { method, param });
  1113. }
  1114. } else {
  1115. // Incorrecly terminated open brace
  1116. result.add(new String[] { method });
  1117. }
  1118. } else {
  1119. String method = calls.substring(start, i);
  1120. result.add(new String[] { method });
  1121. }
  1122. }
  1123. i++;
  1124. }
  1125. return result;
  1126. }
  1127. /**
  1128. * Accepts a string like [author:lower] or [title:abbr] or [auth],
  1129. * whereas the first part signifies the bibtex-field to get, or the key generator
  1130. * field marker to use, while the others are the modifiers that will be applied.
  1131. *
  1132. * @param fieldAndFormat
  1133. * @param entry
  1134. * @param database
  1135. * @return
  1136. */
  1137. public static String getFieldAndFormat(String fieldAndFormat, BibtexEntry entry,
  1138. BibtexDatabase database) {
  1139. fieldAndFormat = stripBrackets(fieldAndFormat);
  1140. int colon = fieldAndFormat.indexOf(':');
  1141. String beforeColon, afterColon;
  1142. if (colon == -1) {
  1143. beforeColon = fieldAndFormat;
  1144. afterColon = null;
  1145. } else {
  1146. beforeColon = fieldAndFormat.substring(0, colon);
  1147. afterColon = fieldAndFormat.substring(colon + 1);
  1148. }
  1149. beforeColon = beforeColon.trim();
  1150. if (beforeColon.length() == 0) {
  1151. return null;
  1152. }
  1153. String fieldValue = BibtexDatabase.getResolvedField(beforeColon, entry, database);
  1154. // If no field value was found, try to interpret it as a key generator field marker:
  1155. if (fieldValue == null)
  1156. fieldValue = LabelPatternUtil.makeLabel(entry, beforeColon);
  1157. if (fieldValue == null)
  1158. return null;
  1159. if (afterColon == null || afterColon.length() == 0)
  1160. return fieldValue;
  1161. String[] parts = afterColon.split(":");
  1162. fieldValue = LabelPatternUtil.applyModifiers(fieldValue, parts, 0);
  1163. return fieldValue;
  1164. }
  1165. /**
  1166. * Convenience function for absolute search.
  1167. *
  1168. * Uses findFile(BibtexEntry, BibtexDatabase, (String)null, String, false).
  1169. */
  1170. public static String findFile(BibtexEntry entry, BibtexDatabase database, String file) {
  1171. return findFile(entry, database, (String) null, file, false);
  1172. }
  1173. /**
  1174. * Internal Version of findFile, which also accepts a current directory to
  1175. * base the search on.
  1176. *
  1177. */
  1178. public static String findFile(BibtexEntry entry, BibtexDatabase database, String directory,
  1179. String file, boolean relative) {
  1180. File root;
  1181. if (directory == null) {
  1182. root = new File(".");
  1183. } else {
  1184. root = new File(directory);
  1185. }
  1186. if (!root.exists())
  1187. return null;
  1188. String found = findFile(entry, database, root, file);
  1189. if (directory == null || !relative) {
  1190. return found;
  1191. }
  1192. if (found != null) {
  1193. try {
  1194. /**
  1195. * [ 1601651 ] PDF subdirectory - missing first character
  1196. *
  1197. * http://sourceforge.net/tracker/index.php?func=detail&aid=1601651&group_id=92314&atid=600306
  1198. */
  1199. // Changed by M. Alver 2007.01.04:
  1200. // Remove first character if it is a directory separator character:
  1201. String tmp = found.substring(root.getCanonicalPath().length());
  1202. if ((tmp.length() > 1) && (tmp.charAt(0) == File.separatorChar))
  1203. tmp = tmp.substring(1);
  1204. return tmp;
  1205. //return found.substring(root.getCanonicalPath().length());
  1206. } catch (IOException e) {
  1207. return null;
  1208. }
  1209. }
  1210. return null;
  1211. }
  1212. /**
  1213. * The actual work-horse. Will find absolute filepaths starting from the
  1214. * given directory using the given regular expression string for search.
  1215. */
  1216. protected static String findFile(BibtexEntry entry, BibtexDatabase database, File directory,
  1217. String file) {
  1218. if (file.startsWith("/")) {
  1219. directory = new File(".");
  1220. file = file.substring(1);
  1221. }
  1222. // Escape handling...
  1223. Matcher m = Pattern.compile("([^\\\\])\\\\([^\\\\])").matcher(file);
  1224. StringBuffer s = new StringBuffer();
  1225. while (m.find()) {
  1226. m.appendReplacement(s, m.group(1) + "/" + m.group(2));
  1227. }
  1228. m.appendTail(s);
  1229. file = s.toString();
  1230. String[] fileParts = file.split("/");
  1231. if (fileParts.length == 0)
  1232. return null;
  1233. if (fileParts.length > 1) {
  1234. for (int i = 0; i < fileParts.length - 1; i++) {
  1235. String dirToProcess = fileParts[i];
  1236. dirToProcess = expandBrackets(dirToProcess, entry, database);
  1237. if (dirToProcess.matches("^.:$")) { // Windows Drive Letter
  1238. directory = new File(dirToProcess + "/");
  1239. continue;
  1240. }
  1241. if (dirToProcess.equals(".")) { // Stay in current directory
  1242. continue;
  1243. }
  1244. if (dirToProcess.equals("..")) {
  1245. directory = new File(directory.getParent());
  1246. continue;
  1247. }
  1248. if (dirToProcess.equals("*")) { // Do for all direct subdirs
  1249. File[] subDirs = directory.listFiles();
  1250. if (subDirs == null)
  1251. return null; // No permission?
  1252. String restOfFileString = join(fileParts, "/", i + 1, fileParts.length);
  1253. for (File subDir : subDirs) {
  1254. if (subDir.isDirectory()) {
  1255. String result = findFile(entry, database, subDir,
  1256. restOfFileString);
  1257. if (result != null)
  1258. return result;
  1259. }
  1260. }
  1261. return null;
  1262. }
  1263. // Do for all direct and indirect subdirs
  1264. if (dirToProcess.equals("**")) {
  1265. List<File> toDo = new LinkedList<File>();
  1266. toDo.add(directory);
  1267. String restOfFileString = join(fileParts, "/", i + 1, fileParts.length);
  1268. // Before checking the subdirs, we first check the current
  1269. // dir
  1270. String result = findFile(entry, database, directory, restOfFileString);
  1271. if (result != null)
  1272. return result;
  1273. while (!toDo.isEmpty()) {
  1274. // Get all subdirs of each of the elements found in toDo
  1275. File[] subDirs = toDo.remove(0).listFiles();
  1276. if (subDirs == null) // No permission?
  1277. continue;
  1278. toDo.addAll(Arrays.asList(subDirs));
  1279. for (File subDir : subDirs) {
  1280. if (!subDir.isDirectory())
  1281. continue;
  1282. result = findFile(entry, database, subDir, restOfFileString);
  1283. if (result != null)
  1284. return result;
  1285. }
  1286. }
  1287. // We already did the currentDirectory
  1288. return null;
  1289. }
  1290. final Pattern toMatch = Pattern
  1291. .compile(dirToProcess.replaceAll("\\\\\\\\", "\\\\"));
  1292. File[] matches = directory.listFiles(new FilenameFilter() {
  1293. public boolean accept(File arg0, String arg1) {
  1294. return toMatch.matcher(arg1).matches();
  1295. }
  1296. });
  1297. if (matches == null || matches.length == 0)
  1298. return null;
  1299. directory = matches[0];
  1300. if (!directory.exists())
  1301. return null;
  1302. } // End process directory information
  1303. }
  1304. // Last step check if the given file can be found in this directory
  1305. String filenameToLookFor = expandBrackets(fileParts[fileParts.length - 1], entry, database);
  1306. final Pattern toMatch = Pattern.compile("^"
  1307. + filenameToLookFor.replaceAll("\\\\\\\\", "\\\\") + "$");
  1308. File[] matches = directory.listFiles(new FilenameFilter() {
  1309. public boolean accept(File arg0, String arg1) {
  1310. return toMatch.matcher(arg1).matches();
  1311. }
  1312. });
  1313. if (matches == null || matches.length == 0)
  1314. return null;
  1315. try {
  1316. return matches[0].getCanonicalPath();
  1317. } catch (IOException e) {
  1318. return null;
  1319. }
  1320. }
  1321. static Pattern squareBracketsPattern = Pattern.compile("\\[.*?\\]");
  1322. /**
  1323. * Takes a string that contains bracketed expression and expands each of
  1324. * these using ge…

Large files files are truncated, but you can click here to view the full file