diff --git a/CHANGELOG.md b/CHANGELOG.md index 3814445e8c59..f4c33fe3b353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,185 @@ +# Changelog +All notable changes to this project will be documented in this file. +This project **does not** adhere to [Semantic Versioning](http://semver.org/). +This file tries to follow the conventions proposed by [keepachangelog.com](http://keepachangelog.com/). +Here, the categories "Changed" for added and changed functionality, +"Fixed" for fixed functionality, and +"Removed" for removed functionality is used. + +We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `#NUM`, +to [sourceforge bugs](https://sourceforge.net/p/jabref/bugs/) by using `bug NUM`, and +to [sourceforge feature requests](https://sourceforge.net/p/jabref/features/) by using `feature NUM`. + +## [Unreleased] + +### Changed +- Fixed #479: Added BufferedInputStream so we can reset the mark +- Add options to close other/all databases in tab right-click menu +- Implements #470: Show editor (as an alternative to author) and booktitle (as an alternative to journal) in the main table by default +- Restore focus to last focused tab on start +- Add ability to format/cleanup the date field +- Add support for proxy authentication via VM args and GUI settings, this implements Feature Request #388 +- Move Bibtex and Biblatex mode switcher to File menu +- Display active edit mode (BibTeX or Biblatex) at window title +- Implements #444: The search is cleared by either clicking the clear-button or by pressing ESC with having focus in the search field. +- Icons are shown as Header for icon columns in the entry table (#315) +- Tooltips are shown for header columns and contents which are too wide to be displayed in the entry table (#384) +- Default order in entry table: # | all file based icons (file, URL/DOI, ...) | all bibtex field based icons (bibtexkey, entrytype, author, title, ...) | all activated special field icons (ranking, quality, ...) + +### Fixed +- Fixed #434: Revert to old 'JabRef' installation folder name instead of 'jabref' +- Fixed #435: Retrieve non open access ScienceDirect PDFs via HTTP DOM +- Fixed: Cleanup process aborts if linked file does not exists +- Fixed #420: Reenable preference changes +- Fixed #414: Rework BibLatex entry types with correct required and optional fields +- Fixed #413: Help links in released jar version are not working +- Fixes #412: Biblatex preserves capital letters, checking whether letters may be converted to lowercase within the Integrity Check action is obsolete. +- Fixed #437: The toolbar after the search field is now correctly wrapped when using a small window size for JabRef +- Fixed #438: Cut, Copy and Paste are now translated correctly in the menu +- Fixed #443/#445: Fixed sorting and moving special field columns +- Fixed #498: non-working legacy PDF/PS column removed +- Fixed #473: Import/export to external database works again + +### Removed +- Removed file history size preference (never available from the UI) +- Removed jstorImporter because it's hardly ever used, even Jstor.org doesn't support/export said format anymore +- Removed option "Show one letter heading for icon columns" which is obsolete with the fix of #315/384 +- Removed table column "PDF/PS" which refers to legacy fields "ps" resp. "pdf" which are no longer supported (see also fix #498) + + + +## [3.0] - 2015-11-29 + +### Changed + - Updated to support OpenOffice 4 and LibreOffice 5 + - Add toolbar icon for deleting an entry, and move menu item for this action to BibTeX + - Better support for IEEEtranBSTCTL entries + - Quick selection of month in entry editor + - Unknown entry types will be converted to 'Misc' (was 'Other' before). + - EntryTypes are now clustered per group on the 'new entry' GUI screen. + - Tab shows the minimal unique folder name substring if multiple database files share the same name + - Added a page numbers integrity checker + - Position and size of certain dialogs are stored and restored. + - Feature: Search Springer + - Feature: Search DOAJ, Directory of Open Access Journals + - Changes the old integrity check by improving the code base (+tests) and converting it to a simple issues table + - Added combo box in MassSetFieldAction to simplify selecting the correct field name + - Feature: Merge information from both entries on duplication detection + - Always use import inspection dialog on import from file + - All duplicate whitespaces / tabs / newlines are now removed from non-multiline fields + - Improvements to search: + - Search bar is now at the top + - A summary of the search result is shown in textual form in the search bar + - The search text field changes its color based on the search result (red if nothing is found, green if at least one entry is found) + - Autocompletion suggestions are shown in a popup + - Search options are available via a drop-down list, this implements Feature Request #853 + - "Clear search" button also clears search field, this implements Feature Request #601 + - Every search is done automatically (live) as soon as the search text is changed + - Search is local by default. To do a global search, one has to do a local search and then this search can be done globally as well, opening a new window. + - The local search results can be shown in a new window. + - Feature: Merge information from a DOI generated BibTex entry to an entry + - Added more characters to HTML/Unicode converter + - Feature: Push citations to Texmaker ([bug 318](https://sourceforge.net/p/jabref/bugs/318/), [bug 582](https://sourceforge.net/p/jabref/bugs/582/)) + - Case changers improved to honor words (not yet more than single words) within {} + - Feature: Added converters from HTML and Unicode to LaTeX on right click in text fields (#191) + - Feature: Add an option to the FileList context menu to delete an associated file from the file system + - Feature: Field names "Doi", "Ee", and "Url" are now written as "DOI", "EE", and "URL" + - The default language is now automatically set to the system's locale. + - Use correct encoding names (#155) and replace old encoding names in bibtex files. This changes the file header. + - No longer write JabRef version to BibTex file header. + - No longer add blank lines inside a bibtex entry + - Feature: When pasting a Google search URL, meta data will be automatically stripped before insertion. + - Feature: PDF auto download from ACS, arXiv, ScienceDirect, SpringerLink, and Google Scholar + - List of authors is now auto generated `scripts/generate-authors.sh` and inserted into L10N About.html + - Streamline logging API: Replace usages of java.util.logging with commons.logging + - Remove support for custom icon themes. The user has to use the default one. + - Solved feature request #767: New subdatabase based on AUX file (biblatex) + - Feature: DOItoBibTeX fetcher now also handles HTTP URLs + - Feature: "Normalize to BibTeX name format" also removes newlines + - Tweak of preference defaults + - Autolink requires that the filename starts with the given BibTeX key and the default filename patterns is key followed by title + - Default sorting changed + - Default label pattern changed from `[auth][year]` to `[authors3][year]` + - Feature: case changers now leave protected areas (enclosed with curly brakets) alone + - BREAKING: The BibTeX key generator settings from previous versions are lost + - BREAKING: LabelPatterns `[auth.etal]`, `[authEtAl]`, `[authors]`, `[authorsN]`, `[authorLast]` and more to omit spaces and commas (and work as described at http://jabref.sourceforge.net/help/LabelPatterns.php) + - BREAKING: `[keywordN]` returns the Nth keyword (as described in the help) and not the first N keywords + - BREAKING: If field consists of blanks only or an emtpy string, it is not written at all + - Feature: new LabelPattern `[authFirstFull]` returning the last name of the first author and also a "van" or "von" if it exists + - Feature: all new lines when writing an entry are obeying the globally configured new line (File -> newline separator). Affects fields: abstract and review + - Feature: `[veryShortTitle]` and `[shortTitle]` also skip words like "in", "among", "before", ... + - Feature: New LabelPattern `[keywordsN]`, where N is optional. Returns the first N keywords. If no N is specified ("`[keywords]`"), all keywords are returned. Spaces are removed. + - Update supported LookAndFeels + - Show replaced journal abbreviations on console + - Integrated [GVK-Plugin](http://www.gbv.de/wikis/cls/Jabref-GVK-Plugin) + - The three options to manage file references are moved to their own separated group in the Tools menu. + - Default preferences: Remote server (port 6050) always started on first JabRef instance. This prevents JabRef loaded twice when opening a bib file. + +### Fixed + - Fixed the bug that the file encoding was not correctly determined from the first (or second) line + - Fixed #325: Deactivating AutoCompletion crashes EntryEditor + - Fixed bug when having added and then removed a personal journal list, an exception is always shown on startup + - Fixed a bug in the IEEEXploreFetcher + - Fixed [bug 1282](https://sourceforge.net/p/jabref/bugs/1282/) related to backslashes duplication. + - Fixed [bug 1285](https://sourceforge.net/p/jabref/bugs/1285/): Editing position is not lost on saving + - Fixed [bug 1297](https://sourceforge.net/p/jabref/bugs/1297/): No console message on closing + - Fixed #194: JabRef starts again on Win XP and Win Vista + - Fixed: Tooltips are now shown for the #-field when the bibtex entry is incomplete. + - Fixed #173: Personal journal abbreviation list is not loaded twice + - Bugfix: Preview of external journal abbreviation list now displays the correct list + - Fixed #223: Window is displayed in visible area even when having multiple screens + - Localization tweaks: "can not" -> "cannot" and "file name" -> "filename" + - Fixed: When reconfiguring the BibTeX key generator, changes are applied instantly without requiring a restart of JabRef + - Fixed #250: No hard line breaks after 70 chars in serialized JabRef meta data + - Fixed [bug 1296](https://sourceforge.net/p/jabref/bugs/1296/): External links in the help open in the standard browser + - Fixed behavior of opening files: If an existing database is opened, it is focused now instead of opening it twice. + +### Removed + - Entry type 'Other' is not selectable anymore as it is no real entry type. Will be converted to 'Misc'. + - BREAKING: Remove plugin functionality. + - The key bindings for searching specific databases are removed + - Remove option to toggle native file dialog on mac by making JabRef always use native file dialogs on mac + - Remove options to set PDF and PS directories per .bib database as the general options have also been deleted. + - Remove option to disable renaming in FileChooser dialogs. + - Remove option to hide the BibTeX Code tab in the entry editor. + - Remove option to set a custom icon for the external file types. This is not possible anymore with the new icon font. + - Remove legacy options to sync files in the "pdf" or "ps" field + - Remove button to merge entries and keep the old ones. + - Remove non-compact rank symbols in favor of compact rank + - Remove Mr.DLib support as MR.DLib will be shut down in 2015 + - Remove support for key bindings per external application by allowing only the key binding "push to application" for the currently selected external application. + - Remove "edit preamble" from toolbar + - Remove support to the move-to-SysTray action + - Remove incremental search + - Remove option to disable autocompleters for search and make this always one + - Remove option to highlight matches and make this always one when not using regex or grammar-based search + - Remove non-working web searches: JSTOR and Sciencedirect (planned to be fixed for the next release) + - Remove option Tools -> Open PDF or PS which is replaced by Tools -> Open File + +## 2.80 - never released + +Version 2.80 was intended as intermediate step to JabRef 3.0. +Since much functionality has changed during development, a release of this version was skipped. + +## [dev_2.11] - unreleased + +### Fixed + - Fix #345: Remove history from help file + +## [2.11.1] - 2015-11-16 + +### Fixed + - Backports from 2.80: Fixed #250: No hard line breaks after 70 chars in serialized JabRef meta data + - Backports from 2.80: Fixed #325: Deactivating AutoCompletion crashes EntryEditor + +## 2.11 - 2015-11-11 + +The changelog of 2.11 and versions before is maintained as [text file](https://github.com/JabRef/jabref/blob/dev_2.11/CHANGELOG) in the [dev_2.11 branch](https://github.com/JabRef/jabref/tree/dev_2.11). + +[Unreleased]: https://github.com/JabRef/jabref/compare/v3.0...HEAD +[3.0]: https://github.com/JabRef/jabref/compare/v2.11.1...v3.0 +[dev_2.11]: https://github.com/JabRef/jabref/compare/v2.11.1...dev_2.11 +[2.11.1]: https://github.com/JabRef/jabref/compare/v2.11...v2.11.1 # Changelog All notable changes to this project will be documented in this file. This project **does not** adhere to [Semantic Versioning](http://semver.org/). diff --git a/src/main/java/net/sf/jabref/importer/ImportFormatReader.java b/src/main/java/net/sf/jabref/importer/ImportFormatReader.java index 8b3443ca9c2a..3744d61f9cd3 100644 --- a/src/main/java/net/sf/jabref/importer/ImportFormatReader.java +++ b/src/main/java/net/sf/jabref/importer/ImportFormatReader.java @@ -1,421 +1,426 @@ -/* Copyright (C) 2003-2015 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. -*/ -package net.sf.jabref.importer; - -import java.io.*; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.*; - -import net.sf.jabref.importer.fileformat.*; -import net.sf.jabref.model.database.BibDatabases; -import net.sf.jabref.model.entry.BibEntry; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import net.sf.jabref.*; - -public class ImportFormatReader { - - public static final String BIBTEX_FORMAT = "BibTeX"; - - /** - * all import formats, in the default order of import formats - */ - private final SortedSet formats = new TreeSet<>(); - - private static final Log LOGGER = LogFactory.getLog(ImportFormatReader.class); - - - public void resetImportFormats() { - formats.clear(); - - formats.add(new BiblioscapeImporter()); - formats.add(new BibtexImporter()); - formats.add(new BibteXMLImporter()); - formats.add(new BiomailImporter()); - formats.add(new CopacImporter()); - formats.add(new EndnoteImporter()); - formats.add(new FreeCiteImporter()); - formats.add(new InspecImporter()); - formats.add(new IsiImporter()); - formats.add(new MedlineImporter()); - formats.add(new MedlinePlainImporter()); - formats.add(new MsBibImporter()); - formats.add(new OvidImporter()); - formats.add(new PdfContentImporter()); - formats.add(new PdfXmpImporter()); - formats.add(new RepecNepImporter()); - formats.add(new RisImporter()); - formats.add(new ScifinderImporter()); - formats.add(new SilverPlatterImporter()); - formats.add(new SixpackImporter()); - - /** - * Get custom import formats - */ - for (CustomImportList.Importer importer : Globals.prefs.customImports) { - try { - ImportFormat imFo = importer.getInstance(); - formats.add(imFo); - } catch (Exception e) { - System.err.println("Could not instantiate " + importer.getName() + " importer, will ignore it. Please check if the class is still available."); - e.printStackTrace(); - } - } - } - - /** - * Format for a given CLI-ID. - *

- *

Will return the first format according to the default-order of - * format that matches the given ID.

- * - * @param cliId CLI-Id - * @return Import Format or null if none matches - */ - private ImportFormat getByCliId(String cliId) { - for (ImportFormat format : formats) { - if (format.getCLIId().equals(cliId)) { - return format; - } - } - return null; - } - - public List importFromStream(String format, InputStream in, OutputPrinter status) - throws IOException { - ImportFormat importer = getByCliId(format); - - if (importer == null) { - throw new IllegalArgumentException("Unknown import format: " + format); - } - - List res = importer.importEntries(in, status); - - // Remove all empty entries - if (res != null) { - BibDatabases.purgeEmptyEntries(res); - } - - return res; - } - - public List importFromFile(String format, String filename, OutputPrinter status) - throws IOException { - ImportFormat importer = getByCliId(format); - - if (importer == null) { - throw new IllegalArgumentException("Unknown import format: " + format); - } - - return importFromFile(importer, filename, status); - } - - public List importFromFile(ImportFormat importer, String filename, OutputPrinter status) throws IOException { - File file = new File(filename); - - try (InputStream stream = new FileInputStream(file)) { - - if (!importer.isRecognizedFormat(stream)) { - throw new IOException("Wrong file format"); - } - - return importer.importEntries(stream, status); - } - } - - /** - * All custom importers. - *

- *

Elements are in default order.

- * - * @return all custom importers, elements are of type InputFormat - */ - public SortedSet getCustomImportFormats() { - SortedSet result = new TreeSet<>(); - for (ImportFormat format : formats) { - if (format.getIsCustomImporter()) { - result.add(format); - } - } - return result; - } - - /** - * All built-in importers. - *

- *

Elements are in default order.

- * - * @return all custom importers, elements are of type InputFormat - */ - public SortedSet getBuiltInInputFormats() { - SortedSet result = new TreeSet<>(); - for (ImportFormat format : formats) { - if (!format.getIsCustomImporter()) { - result.add(format); - } - } - return result; - } - - /** - * All importers. - *

- *

- * Elements are in default order. - *

- * - * @return all custom importers, elements are of type InputFormat - */ - public SortedSet getImportFormats() { - return this.formats; - } - - /** - * Human readable list of all known import formats (name and CLI Id). - *

- *

List is in default-order.

- * - * @return human readable list of all known import formats - */ - public String getImportFormatList() { - StringBuilder sb = new StringBuilder(); - - for (ImportFormat imFo : formats) { - int pad = Math.max(0, 14 - imFo.getFormatName().length()); - sb.append(" "); - sb.append(imFo.getFormatName()); - - for (int j = 0; j < pad; j++) { - sb.append(" "); - } - - sb.append(" : "); - sb.append(imFo.getCLIId()); - sb.append("\n"); - } - - return sb.toString(); //.substring(0, res.length()-1); - } - - /** - * Expand initials, e.g. EH Wissler -> E. H. Wissler or Wissler, EH -> Wissler, E. H. - * - * @param name - * @return The name after expanding initials. - */ - public static String expandAuthorInitials(String name) { - String[] authors = name.split(" and "); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < authors.length; i++) { - if (authors[i].contains(", ")) { - String[] names = authors[i].split(", "); - if (names.length > 0) { - sb.append(names[0]); - if (names.length > 1) { - sb.append(", "); - } - } - for (int j = 1; j < names.length; j++) { - if (j == 1) { - sb.append(ImportFormatReader.expandAll(names[j])); - } else { - sb.append(names[j]); - } - if (j < (names.length - 1)) { - sb.append(", "); - } - } - - } else { - String[] names = authors[i].split(" "); - if (names.length > 0) { - sb.append(ImportFormatReader.expandAll(names[0])); - } - for (int j = 1; j < names.length; j++) { - sb.append(" "); - sb.append(names[j]); - } - } - if (i < (authors.length - 1)) { - sb.append(" and "); - } - } - - return sb.toString().trim(); - } - - //------------------------------------------------------------------------------ - - private static String expandAll(String s) { - //System.out.println("'"+s+"'"); - // Avoid arrayindexoutof.... : - if (s.isEmpty()) { - return s; - } - // If only one character (uppercase letter), add a dot and return immediately: - if ((s.length() == 1) && Character.isLetter(s.charAt(0)) && - Character.isUpperCase(s.charAt(0))) { - return s + "."; - } - StringBuilder sb = new StringBuilder(); - char c = s.charAt(0); - char d = 0; - for (int i = 1; i < s.length(); i++) { - d = s.charAt(i); - if (Character.isLetter(c) && Character.isUpperCase(c) && - Character.isLetter(d) && Character.isUpperCase(d)) { - sb.append(c); - sb.append(". "); - } else { - sb.append(c); - } - c = d; - } - if (Character.isLetter(c) && Character.isUpperCase(c) && - Character.isLetter(d) && Character.isUpperCase(d)) { - sb.append(c); - sb.append(". "); - } else { - sb.append(c); - } - return sb.toString().trim(); - } - - static File checkAndCreateFile(String filename) { - File f = new File(filename); - - if (!f.exists() && !f.canRead() && !f.isFile()) { - - LOGGER.info("Error " + filename + " is not a valid file and|or is not readable."); - return null; - - } else { - return f; - } - } - - //================================================== - // Set a field, unless the string to set is empty. - //================================================== - public static void setIfNecessary(BibEntry be, String field, String content) { - if (!"".equals(content)) { - be.setField(field, content); - } - } - - public static InputStreamReader getUTF8Reader(File f) throws IOException { - return getReader(f, StandardCharsets.UTF_8); - } - - public static InputStreamReader getUTF16Reader(File f) throws IOException { - return getReader(f, StandardCharsets.UTF_16); - } - - public static InputStreamReader getReader(File f, Charset charset) - throws IOException { - return new InputStreamReader(new FileInputStream(f), charset); - } - - public static Reader getReaderDefaultEncoding(InputStream in) - throws IOException { - InputStreamReader reader; - reader = new InputStreamReader(in, Globals.prefs.getDefaultEncoding()); - - return reader; - } - - public static class UnknownFormatImport { - - public final String format; - public final ParserResult parserResult; - - - public UnknownFormatImport(String format, ParserResult parserResult) { - this.format = format; - this.parserResult = parserResult; - } - } - - - /** - * Tries to import a file by iterating through the available import filters, - * and keeping the import that seems most promising. - *

- * If all fails this method attempts to read this file as bibtex. - * - * @throws IOException - */ - public UnknownFormatImport importUnknownFormat(String filename) { - - // we don't use a provided OutputPrinter (such as the JabRef frame), - // as we don't want to see any outputs from failed importers: - // we expect failures and do not want to report them to the user - OutputPrinterToNull nullOutput = new OutputPrinterToNull(); - - // stores ref to best result, gets updated at the next loop - List bestResult = null; - int bestResultCount = 0; - String bestFormatName = null; - - // Cycle through all importers: - for (ImportFormat imFo : getImportFormats()) { - - try { - - List entries = importFromFile(imFo, filename, nullOutput); - - int entryCount; - if (entries == null) { - entryCount = 0; - } else { - BibDatabases.purgeEmptyEntries(entries); - entryCount = entries.size(); - } - - if (entryCount > bestResultCount) { - bestResult = entries; - bestResultCount = bestResult.size(); - bestFormatName = imFo.getFormatName(); - } - } catch (IOException ex) { - // The import didn't succeed. Go on. - } - } - - if (bestResult != null) { - // we found something - ParserResult parserResult = new ParserResult(bestResult); - return new UnknownFormatImport(bestFormatName, parserResult); - } - - // Finally, if all else fails, see if it is a BibTeX file: - try { - ParserResult pr = OpenDatabaseAction.loadDatabase(new File(filename), - Globals.prefs.getDefaultEncoding()); - if ((pr.getDatabase().getEntryCount() > 0) - || (pr.getDatabase().getStringCount() > 0)) { - pr.setFile(new File(filename)); - return new UnknownFormatImport(ImportFormatReader.BIBTEX_FORMAT, pr); - } - } catch (Throwable ex) { - return null; - } - - return null; - } -} +/* Copyright (C) 2003-2015 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. +*/ +package net.sf.jabref.importer; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import net.sf.jabref.importer.fileformat.*; +import net.sf.jabref.model.database.BibDatabases; +import net.sf.jabref.model.entry.BibEntry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import net.sf.jabref.*; + +public class ImportFormatReader { + + public static final String BIBTEX_FORMAT = "BibTeX"; + + /** + * all import formats, in the default order of import formats + */ + private final SortedSet formats = new TreeSet<>(); + + private static final Log LOGGER = LogFactory.getLog(ImportFormatReader.class); + + + public void resetImportFormats() { + formats.clear(); + + formats.add(new BiblioscapeImporter()); + formats.add(new BibtexImporter()); + formats.add(new BibteXMLImporter()); + formats.add(new BiomailImporter()); + formats.add(new CopacImporter()); + formats.add(new EndnoteImporter()); + formats.add(new FreeCiteImporter()); + formats.add(new InspecImporter()); + formats.add(new IsiImporter()); + formats.add(new MedlineImporter()); + formats.add(new MedlinePlainImporter()); + formats.add(new MsBibImporter()); + formats.add(new OvidImporter()); + formats.add(new PdfContentImporter()); + formats.add(new PdfXmpImporter()); + formats.add(new RepecNepImporter()); + formats.add(new RisImporter()); + formats.add(new ScifinderImporter()); + formats.add(new SilverPlatterImporter()); + formats.add(new SixpackImporter()); + + /** + * Get custom import formats + */ + for (CustomImportList.Importer importer : Globals.prefs.customImports) { + try { + ImportFormat imFo = importer.getInstance(); + formats.add(imFo); + } catch (Exception e) { + System.err.println("Could not instantiate " + importer.getName() + " importer, will ignore it. Please check if the class is still available."); + e.printStackTrace(); + } + } + } + + /** + * Format for a given CLI-ID. + *

+ *

Will return the first format according to the default-order of + * format that matches the given ID.

+ * + * @param cliId CLI-Id + * @return Import Format or null if none matches + */ + private ImportFormat getByCliId(String cliId) { + for (ImportFormat format : formats) { + if (format.getCLIId().equals(cliId)) { + return format; + } + } + return null; + } + + public List importFromStream(String format, InputStream in, OutputPrinter status) + throws IOException { + ImportFormat importer = getByCliId(format); + + if (importer == null) { + throw new IllegalArgumentException("Unknown import format: " + format); + } + + List res = importer.importEntries(in, status); + + // Remove all empty entries + if (res != null) { + BibDatabases.purgeEmptyEntries(res); + } + + return res; + } + + public List importFromFile(String format, String filename, OutputPrinter status) + throws IOException { + ImportFormat importer = getByCliId(format); + + if (importer == null) { + throw new IllegalArgumentException("Unknown import format: " + format); + } + + return importFromFile(importer, filename, status); + } + + public List importFromFile(ImportFormat importer, String filename, OutputPrinter status) throws IOException { + File file = new File(filename); + + try (InputStream stream = new FileInputStream(file); + BufferedInputStream bis = new BufferedInputStream(stream)) { + + bis.mark(Integer.MAX_VALUE); + + if (!importer.isRecognizedFormat(bis)) { + throw new IOException("Wrong file format"); + } + + bis.reset(); + + return importer.importEntries(bis, status); + } + } + + /** + * All custom importers. + *

+ *

Elements are in default order.

+ * + * @return all custom importers, elements are of type InputFormat + */ + public SortedSet getCustomImportFormats() { + SortedSet result = new TreeSet<>(); + for (ImportFormat format : formats) { + if (format.getIsCustomImporter()) { + result.add(format); + } + } + return result; + } + + /** + * All built-in importers. + *

+ *

Elements are in default order.

+ * + * @return all custom importers, elements are of type InputFormat + */ + public SortedSet getBuiltInInputFormats() { + SortedSet result = new TreeSet<>(); + for (ImportFormat format : formats) { + if (!format.getIsCustomImporter()) { + result.add(format); + } + } + return result; + } + + /** + * All importers. + *

+ *

+ * Elements are in default order. + *

+ * + * @return all custom importers, elements are of type InputFormat + */ + public SortedSet getImportFormats() { + return this.formats; + } + + /** + * Human readable list of all known import formats (name and CLI Id). + *

+ *

List is in default-order.

+ * + * @return human readable list of all known import formats + */ + public String getImportFormatList() { + StringBuilder sb = new StringBuilder(); + + for (ImportFormat imFo : formats) { + int pad = Math.max(0, 14 - imFo.getFormatName().length()); + sb.append(" "); + sb.append(imFo.getFormatName()); + + for (int j = 0; j < pad; j++) { + sb.append(" "); + } + + sb.append(" : "); + sb.append(imFo.getCLIId()); + sb.append("\n"); + } + + return sb.toString(); //.substring(0, res.length()-1); + } + + /** + * Expand initials, e.g. EH Wissler -> E. H. Wissler or Wissler, EH -> Wissler, E. H. + * + * @param name + * @return The name after expanding initials. + */ + public static String expandAuthorInitials(String name) { + String[] authors = name.split(" and "); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < authors.length; i++) { + if (authors[i].contains(", ")) { + String[] names = authors[i].split(", "); + if (names.length > 0) { + sb.append(names[0]); + if (names.length > 1) { + sb.append(", "); + } + } + for (int j = 1; j < names.length; j++) { + if (j == 1) { + sb.append(ImportFormatReader.expandAll(names[j])); + } else { + sb.append(names[j]); + } + if (j < (names.length - 1)) { + sb.append(", "); + } + } + + } else { + String[] names = authors[i].split(" "); + if (names.length > 0) { + sb.append(ImportFormatReader.expandAll(names[0])); + } + for (int j = 1; j < names.length; j++) { + sb.append(" "); + sb.append(names[j]); + } + } + if (i < (authors.length - 1)) { + sb.append(" and "); + } + } + + return sb.toString().trim(); + } + + //------------------------------------------------------------------------------ + + private static String expandAll(String s) { + //System.out.println("'"+s+"'"); + // Avoid arrayindexoutof.... : + if (s.isEmpty()) { + return s; + } + // If only one character (uppercase letter), add a dot and return immediately: + if ((s.length() == 1) && Character.isLetter(s.charAt(0)) && + Character.isUpperCase(s.charAt(0))) { + return s + "."; + } + StringBuilder sb = new StringBuilder(); + char c = s.charAt(0); + char d = 0; + for (int i = 1; i < s.length(); i++) { + d = s.charAt(i); + if (Character.isLetter(c) && Character.isUpperCase(c) && + Character.isLetter(d) && Character.isUpperCase(d)) { + sb.append(c); + sb.append(". "); + } else { + sb.append(c); + } + c = d; + } + if (Character.isLetter(c) && Character.isUpperCase(c) && + Character.isLetter(d) && Character.isUpperCase(d)) { + sb.append(c); + sb.append(". "); + } else { + sb.append(c); + } + return sb.toString().trim(); + } + + static File checkAndCreateFile(String filename) { + File f = new File(filename); + + if (!f.exists() && !f.canRead() && !f.isFile()) { + + LOGGER.info("Error " + filename + " is not a valid file and|or is not readable."); + return null; + + } else { + return f; + } + } + + //================================================== + // Set a field, unless the string to set is empty. + //================================================== + public static void setIfNecessary(BibEntry be, String field, String content) { + if (!"".equals(content)) { + be.setField(field, content); + } + } + + public static InputStreamReader getUTF8Reader(File f) throws IOException { + return getReader(f, StandardCharsets.UTF_8); + } + + public static InputStreamReader getUTF16Reader(File f) throws IOException { + return getReader(f, StandardCharsets.UTF_16); + } + + public static InputStreamReader getReader(File f, Charset charset) + throws IOException { + return new InputStreamReader(new FileInputStream(f), charset); + } + + public static Reader getReaderDefaultEncoding(InputStream in) + throws IOException { + InputStreamReader reader; + reader = new InputStreamReader(in, Globals.prefs.getDefaultEncoding()); + + return reader; + } + + public static class UnknownFormatImport { + + public final String format; + public final ParserResult parserResult; + + + public UnknownFormatImport(String format, ParserResult parserResult) { + this.format = format; + this.parserResult = parserResult; + } + } + + + /** + * Tries to import a file by iterating through the available import filters, + * and keeping the import that seems most promising. + *

+ * If all fails this method attempts to read this file as bibtex. + * + * @throws IOException + */ + public UnknownFormatImport importUnknownFormat(String filename) { + + // we don't use a provided OutputPrinter (such as the JabRef frame), + // as we don't want to see any outputs from failed importers: + // we expect failures and do not want to report them to the user + OutputPrinterToNull nullOutput = new OutputPrinterToNull(); + + // stores ref to best result, gets updated at the next loop + List bestResult = null; + int bestResultCount = 0; + String bestFormatName = null; + + // Cycle through all importers: + for (ImportFormat imFo : getImportFormats()) { + + try { + + List entries = importFromFile(imFo, filename, nullOutput); + + int entryCount; + if (entries == null) { + entryCount = 0; + } else { + BibDatabases.purgeEmptyEntries(entries); + entryCount = entries.size(); + } + + if (entryCount > bestResultCount) { + bestResult = entries; + bestResultCount = bestResult.size(); + bestFormatName = imFo.getFormatName(); + } + } catch (IOException ex) { + // The import didn't succeed. Go on. + } + } + + if (bestResult != null) { + // we found something + ParserResult parserResult = new ParserResult(bestResult); + return new UnknownFormatImport(bestFormatName, parserResult); + } + + // Finally, if all else fails, see if it is a BibTeX file: + try { + ParserResult pr = OpenDatabaseAction.loadDatabase(new File(filename), + Globals.prefs.getDefaultEncoding()); + if ((pr.getDatabase().getEntryCount() > 0) + || (pr.getDatabase().getStringCount() > 0)) { + pr.setFile(new File(filename)); + return new UnknownFormatImport(ImportFormatReader.BIBTEX_FORMAT, pr); + } + } catch (Throwable ex) { + return null; + } + + return null; + } +}