Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for issue 3143: Import entry from clipboard in different formats #3243

Merged
merged 8 commits into from
Oct 6, 2017
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ We refer to [GitHub issues](https://github.com/JabRef/jabref/issues) by using `#
- We added bracketed expresion support for file search patterns, import file name patterns and file directory patters, in addition to bibtexkey patterns.
- We added support for '[entrytype]' bracketed expression.
- Updated French translation
- We added support for pasting entries in different formats [#3143](https://github.com/JabRef/jabref/issues/3143)

### Fixed
- We fixed an issue where JabRef would not terminated after asking to collect anonymous statistics [#2955 comment](https://github.com/JabRef/jabref/issues/2955#issuecomment-334591123)
Expand Down
41 changes: 26 additions & 15 deletions src/main/java/org/jabref/gui/ClipBoardManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,38 @@
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.jabref.Globals;
import org.jabref.logic.importer.FetcherException;
import org.jabref.logic.importer.ImportException;
import org.jabref.logic.importer.ImportFormatReader;
import org.jabref.logic.importer.ImportFormatReader.UnknownFormatImport;
import org.jabref.logic.importer.fetcher.DoiFetcher;
import org.jabref.logic.importer.fileformat.BibtexParser;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.identifier.DOI;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ClipBoardManager implements ClipboardOwner {

private static final Log LOGGER = LogFactory.getLog(ClipBoardManager.class);

private static final Clipboard CLIPBOARD = Toolkit.getDefaultToolkit().getSystemClipboard();
private final Clipboard clipboard;

private final ImportFormatReader importFormatReader;

public ClipBoardManager() {
this(Toolkit.getDefaultToolkit().getSystemClipboard(), Globals.IMPORT_FORMAT_READER);
}

public ClipBoardManager(Clipboard clipboard, ImportFormatReader importFormatReader) {
this.clipboard = clipboard;
this.importFormatReader = importFormatReader;
}

/**
* Empty implementation of the ClipboardOwner interface.
Expand All @@ -41,7 +53,7 @@ public void lostOwnership(Clipboard aClipboard, Transferable aContents) {
* Places the string into the clipboard using a {@link Transferable}.
*/
public void setTransferableClipboardContents(Transferable transferable) {
CLIPBOARD.setContents(transferable, this);
clipboard.setContents(transferable, this);
}

/**
Expand All @@ -53,7 +65,7 @@ public void setTransferableClipboardContents(Transferable transferable) {
public String getClipboardContents() {
String result = "";
//odd: the Object param of getContents is not currently used
Transferable contents = CLIPBOARD.getContents(null);
Transferable contents = clipboard.getContents(null);
if ((contents != null) && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
try {
result = (String) contents.getTransferData(DataFlavor.stringFlavor);
Expand All @@ -71,17 +83,17 @@ public String getClipboardContents() {
*/
public void setClipboardContents(String aString) {
StringSelection stringSelection = new StringSelection(aString);
CLIPBOARD.setContents(stringSelection, this);
clipboard.setContents(stringSelection, this);
}

public List<BibEntry> extractBibEntriesFromClipboard() {
// Get clipboard contents, and see if TransferableBibtexEntry is among the content flavors offered
Transferable content = CLIPBOARD.getContents(null);
Transferable content = clipboard.getContents(null);
List<BibEntry> result = new ArrayList<>();

if (content.isDataFlavorSupported(TransferableBibtexEntry.ENTRY_FLAVOR)) {
// We have determined that the clipboard data is a set of entries.
try {
try {
@SuppressWarnings("unchecked")
List<BibEntry> contents = (List<BibEntry>) content.getTransferData(TransferableBibtexEntry.ENTRY_FLAVOR);
result = contents;
Expand All @@ -99,12 +111,11 @@ public List<BibEntry> extractBibEntriesFromClipboard() {
Optional<BibEntry> entry = new DoiFetcher(Globals.prefs.getImportFormatPreferences()).performSearchById(new DOI(data).getDOI());
entry.ifPresent(result::add);
} else {
// parse bibtex string
BibtexParser bp = new BibtexParser(Globals.prefs.getImportFormatPreferences());
BibDatabase db = bp.parse(new StringReader(data)).getDatabase();
LOGGER.info("Parsed " + db.getEntryCount() + " entries from clipboard text");
if (db.hasEntries()) {
result = db.getEntries();
try {
UnknownFormatImport unknownFormatImport = importFormatReader.importUnknownFormat(data);
result = unknownFormatImport.parserResult.getDatabase().getEntries();
} catch (ImportException e) {
// import failed and result will be empty
}
}
} catch (UnsupportedFlavorException ex) {
Expand Down
79 changes: 57 additions & 22 deletions src/main/java/org/jabref/logic/importer/ImportFormatReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -156,28 +156,22 @@ public UnknownFormatImport(String format, ParserResult parserResult) {
}
}

@FunctionalInterface
public static interface CheckedFunction<T, R> {

R apply(T t) throws IOException;
}

/**
* Tries to import a file by iterating through the available import filters,
* and keeping the import that seems most promising.
* <p/>
* If all fails this method attempts to read this file as bibtex.
* Tries to import entries by iterating through the available import filters,
* and keeping the import that seems the most promising
*
* @param importDatabase the function to import the entries with a formatter
* @param isRecognizedFormat the function to check whether the source is in the correct format for an importer
* @return an UnknownFormatImport with the imported entries and metadata
* @throws ImportException if the import fails (for example, if no suitable importer is found)
*/
public UnknownFormatImport importUnknownFormat(Path filePath) throws ImportException {
Objects.requireNonNull(filePath);

// First, see if it is a BibTeX file:
try {
ParserResult parserResult = OpenDatabase.loadDatabase(filePath.toFile(), importFormatPreferences);
if (parserResult.getDatabase().hasEntries() || !parserResult.getDatabase().hasNoStrings()) {
parserResult.setFile(filePath.toFile());
return new UnknownFormatImport(ImportFormatReader.BIBTEX_FORMAT, parserResult);
}
} catch (IOException ignore) {
// Ignored
}

private UnknownFormatImport importUnknownFormat(CheckedFunction<Importer, ParserResult> importDatabase, CheckedFunction<Importer, Boolean> isRecognizedFormat) throws ImportException {
// stores ref to best result, gets updated at the next loop
List<BibEntry> bestResult = null;
int bestResultCount = 0;
Expand All @@ -186,19 +180,19 @@ public UnknownFormatImport importUnknownFormat(Path filePath) throws ImportExcep
// Cycle through all importers:
for (Importer imFo : getImportFormats()) {
try {
if (!imFo.isRecognizedFormat(filePath, importFormatPreferences.getEncoding())) {
if (!isRecognizedFormat.apply(imFo)) {
continue;
}

ParserResult parserResult = imFo.importDatabase(filePath, importFormatPreferences.getEncoding());
ParserResult parserResult = importDatabase.apply(imFo);
List<BibEntry> entries = parserResult.getDatabase().getEntries();

BibDatabases.purgeEmptyEntries(entries);
int entryCount = entries.size();

if (entryCount > bestResultCount) {
bestResult = entries;
bestResultCount = bestResult.size();
bestResultCount = entryCount;
bestFormatName = imFo.getName();
}
} catch (IOException ex) {
Expand All @@ -209,10 +203,51 @@ public UnknownFormatImport importUnknownFormat(Path filePath) throws ImportExcep
if (bestResult != null) {
// we found something
ParserResult parserResult = new ParserResult(bestResult);
parserResult.setFile(filePath.toFile());
return new UnknownFormatImport(bestFormatName, parserResult);
}

throw new ImportException(Localization.lang("Could not find a suitable import format."));
}

/**
* Tries to import a file by iterating through the available import filters,
* and keeping the import that seems most promising.
* <p/>
* This method first attempts to read this file as bibtex.
*
* @throws ImportException if the import fails (for example, if no suitable importer is found)
*/
public UnknownFormatImport importUnknownFormat(Path filePath) throws ImportException {
Objects.requireNonNull(filePath);

// First, see if it is a BibTeX file:
try {
ParserResult parserResult = OpenDatabase.loadDatabase(filePath.toFile(), importFormatPreferences);
if (parserResult.getDatabase().hasEntries() || !parserResult.getDatabase().hasNoStrings()) {
parserResult.setFile(filePath.toFile());
return new UnknownFormatImport(ImportFormatReader.BIBTEX_FORMAT, parserResult);
}
} catch (IOException ignore) {
// Ignored
}

UnknownFormatImport unknownFormatImport = importUnknownFormat(importer -> importer.importDatabase(filePath, importFormatPreferences.getEncoding()), importer -> importer.isRecognizedFormat(filePath, importFormatPreferences.getEncoding()));
unknownFormatImport.parserResult.setFile(filePath.toFile());
return unknownFormatImport;
}

/**
* Tries to import a String by iterating through the available import filters,
* and keeping the import that seems the most promising
*
* @param data the string to import
* @return an UnknownFormatImport with the imported entries and metadata
* @throws ImportException if the import fails (for example, if no suitable importer is found)
*/
public UnknownFormatImport importUnknownFormat(String data) throws ImportException {
Objects.requireNonNull(data);

return importUnknownFormat(importer -> importer.importDatabase(data), importer -> importer.isRecognizedFormat(data));
}

}
45 changes: 42 additions & 3 deletions src/main/java/org/jabref/logic/importer/Importer.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
Expand All @@ -16,6 +17,7 @@
* Role of an importer for JabRef.
*/
public abstract class Importer implements Comparable<Importer> {

/**
* Check whether the source is in the correct format for this importer.
*
Expand All @@ -28,12 +30,33 @@ public abstract class Importer implements Comparable<Importer> {
*/
public abstract boolean isRecognizedFormat(BufferedReader input) throws IOException;

/**
* Check whether the source is in the correct format for this importer.
*
* @param filePath the path of the file to check
* @param encoding the encoding of the file
* @return true, if the file is in a recognized format
* @throws IOException Signals that an I/O exception has occurred.
*/
public boolean isRecognizedFormat(Path filePath, Charset encoding) throws IOException {
try (BufferedReader bufferedReader = getReader(filePath, encoding)) {
return isRecognizedFormat(bufferedReader);
}
}

/**
* Check whether the source is in the correct format for this importer.
*
* @param data the data to check
* @return true, if the data is in a recognized format
* @throws IOException Signals that an I/O exception has occurred.
*/
public boolean isRecognizedFormat(String data) throws IOException {
try (StringReader stringReader = new StringReader(data); BufferedReader bufferedReader = new BufferedReader(stringReader)) {
return isRecognizedFormat(bufferedReader);
}
}

/**
* Parse the database in the source.
*
Expand All @@ -49,7 +72,7 @@ public boolean isRecognizedFormat(Path filePath, Charset encoding) throws IOExce
*
* @param input the input to read from
*/
public abstract ParserResult importDatabase(BufferedReader input) throws IOException ;
public abstract ParserResult importDatabase(BufferedReader input) throws IOException;

/**
* Parse the database in the specified file.
Expand All @@ -69,6 +92,23 @@ public ParserResult importDatabase(Path filePath, Charset encoding) throws IOExc
}
}

/**
* Parse the database in the specified string.
*
* Importer having the facilities to detect the correct encoding of a string should overwrite this method,
* determine the encoding and then call {@link #importDatabase(BufferedReader)}.
*
* @param data the string which should be imported
* @return the parsed result
* @throws IOException Signals that an I/O exception has occurred.
*/
public ParserResult importDatabase(String data) throws IOException {
try (StringReader stringReader = new StringReader(data); BufferedReader bufferedReader = new BufferedReader(stringReader)) {
ParserResult parserResult = importDatabase(bufferedReader);
return parserResult;
}
}

protected static BufferedReader getUTF8Reader(Path filePath) throws IOException {
return getReader(filePath, StandardCharsets.UTF_8);
}
Expand All @@ -92,7 +132,6 @@ public static BufferedReader getReader(Path filePath, Charset encoding)
*/
public abstract String getName();


/**
* Returns the file extensions that this importer can read
* @return {@link FileExtensions} correspoding to the importer
Expand Down Expand Up @@ -144,7 +183,7 @@ public boolean equals(Object obj) {
if (!(obj instanceof Importer)) {
return false;
}
Importer other = (Importer)obj;
Importer other = (Importer) obj;
return Objects.equals(this.getName(), other.getName());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
*/
package org.jabref.logic.importer.fetcher;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
Expand Down Expand Up @@ -55,8 +53,8 @@ public List<BibEntry> performSearch(BibEntry entry) throws FetcherException {
MrDLibImporter importer = new MrDLibImporter();
ParserResult parserResult = new ParserResult();
try {
if (importer.isRecognizedFormat(new BufferedReader(new StringReader(response)))) {
parserResult = importer.importDatabase(new BufferedReader(new StringReader(response)));
if (importer.isRecognizedFormat(response)) {
parserResult = importer.importDatabase(response);
} else {
// For displaying An ErrorMessage
BibEntry errorBibEntry = new BibEntry();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,9 @@ private String getSubfield(String a, Element datafield) {
}

private Element getChild(String name, Element e) {
if (e == null) {
return null;
}
NodeList children = e.getChildNodes();

int j = children.getLength();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,9 @@ public boolean isRecognizedFormat(BufferedReader input) throws IOException {
try (InputStream stream = new ByteArrayInputStream(recommendationsAsString.getBytes())) {
saxParser.parse(stream, handler);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
return false;
}
} catch (ParserConfigurationException | SAXException e) {
LOGGER.error(e.getMessage(), e);
return false;
}
return true;
Expand Down
Loading