diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 894b3653e66..b8f22170b1b 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -16,7 +16,7 @@ jobs: env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - name: Merge pull requests - uses: pascalgn/automerge-action@v0.11.0 + uses: pascalgn/automerge-action@v0.12.0 if: steps.waitforstatuschecks.outputs.status == 'success' env: MERGE_METHOD: "squash" diff --git a/AUTHORS b/AUTHORS index 3cfdae2c1ff..48da52cdc5b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -113,6 +113,7 @@ Eiswindyeti Ellen Reitmayr Erdem Derebasoglu Erdem Derebaşoğlu +Eric Lau Erik Putrycz Ervin Kolenovic Escoul @@ -179,6 +180,7 @@ Jens Döcke joeyzgraggen Johannes Hupe Johannes Manner +Johannes Theiner John David John Relph John Zedlewski diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a1922cfff3..45413f5a08e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,12 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We added a query parser and mapping layer to enable conversion of queries formulated in simplified lucene syntax by the user into api queries. [#6799](https://github.com/JabRef/jabref/pull/6799) - We added some basic functionality to customise the look of JabRef by importing a css theme file. [#5790](https://github.com/JabRef/jabref/issues/5790) - We added connection check function in network preference setting [#6560](https://github.com/JabRef/jabref/issues/6560) +- We added a new fetcher to enable users to search jstor.org [#6627](https://github.com/JabRef/jabref/issues/6627) ### Changed +- We changed the default preferences for OpenOffice/LibreOffice integration to automatically sync the bibliography when +inserting new citations in a OpenOffic/LibreOffice document. [#6957](https://github.com/JabRef/jabref/issues/6957) - We restructured the 'File' tab and extracted some parts into the 'Linked files' tab [#6779](https://github.com/JabRef/jabref/pull/6779) - JabRef now offers journal lists from . JabRef the lists which use a dot inside the abbreviations. [#5749](https://github.com/JabRef/jabref/pull/5749) - We removed two useless preferences in the groups preferences dialog. [#6836](https://github.com/JabRef/jabref/pull/6836) @@ -33,6 +36,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We improved the detection of "short" DOIs [6880](https://github.com/JabRef/jabref/issues/6880) - We improved the duplicate detection when identifiers like DOI or arxiv are semantiaclly the same, but just syntactically differ (e.g. with or without http(s):// prefix). [#6707](https://github.com/JabRef/jabref/issues/6707) - We changed in the group interface "Generate groups from keywords in a BibTeX field" by "Generate groups from keywords in the following field". [#6983](https://github.com/JabRef/jabref/issues/6983) +- We changed the name of a group type from "Searching for keywords" to "Searching for a keyword". [6995](https://github.com/JabRef/jabref/pull/6995) ### Fixed @@ -48,6 +52,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed an issue where it was impossible to connect to OpenOffice/LibreOffice on Mac OSX. [#6970](https://github.com/JabRef/jabref/pull/6970) - We fixed an issue with the python script used by browser plugins that failed to locate JabRef if not installed in its default location. [#6963](https://github.com/JabRef/jabref/pull/6963/files) - We fixed an issue where identity column header had incorrect foreground color in the Dark theme. [#6796](https://github.com/JabRef/jabref/issues/6796) +- We fixed an issue where clicking on Collapse All button in the Search for Unlinked Local Files expanded the directory structure erroneously [#6848](https://github.com/JabRef/jabref/issues/6848) ### Removed diff --git a/build.gradle b/build.gradle index 22543d27379..d52be63ae85 100644 --- a/build.gradle +++ b/build.gradle @@ -137,13 +137,13 @@ dependencies { antlr4 'org.antlr:antlr4:4.8-1' implementation 'org.antlr:antlr4-runtime:4.8-1' - implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.6.2') { + implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.6.3') { exclude group: 'org.apache.lucene', module: 'lucene-sandbox' } implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.7.0' - implementation 'org.postgresql:postgresql:42.2.16' + implementation 'org.postgresql:postgresql:42.2.17' implementation ('com.oracle.ojdbc:ojdbc10:19.3.0.0') { // causing module issues @@ -163,14 +163,14 @@ dependencies { implementation 'de.saxsys:mvvmfx-validation:1.9.0-SNAPSHOT' implementation 'de.saxsys:mvvmfx:1.8.0' implementation 'com.tobiasdiez:easybind:2.1.0' - implementation 'org.fxmisc.flowless:flowless:0.6.1' + implementation 'org.fxmisc.flowless:flowless:0.6.2' implementation 'org.fxmisc.richtext:richtextfx:0.10.4' implementation group: 'org.glassfish.hk2.external', name: 'jakarta.inject', version: '2.6.1' implementation 'com.jfoenix:jfoenix:9.0.10' implementation 'org.controlsfx:controlsfx:11.0.2' implementation 'org.jsoup:jsoup:1.13.1' - implementation 'com.konghq:unirest-java:3.11.00' + implementation 'com.konghq:unirest-java:3.11.01' implementation 'org.slf4j:slf4j-api:2.0.0-alpha1' implementation group: 'org.apache.logging.log4j', name: 'log4j-jcl', version: '3.0.0-SNAPSHOT' @@ -204,7 +204,7 @@ dependencies { testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.7.0' testImplementation 'org.junit.platform:junit-platform-launcher:1.7.0' - testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.16' + testImplementation 'net.bytebuddy:byte-buddy-parent:1.10.17' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '3.0.0-SNAPSHOT' testRuntime group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '3.0.0-SNAPSHOT' testImplementation 'org.mockito:mockito-core:3.5.13' diff --git a/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesDialog.java b/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesDialog.java index cbef3f235f6..66c97e29ef1 100644 --- a/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesDialog.java +++ b/src/main/java/org/jabref/gui/externalfiles/FindUnlinkedFilesDialog.java @@ -165,7 +165,7 @@ private void initialize() { buttonOptionCollapseAll.setOnAction(event -> { CheckBoxTreeItem root = (CheckBoxTreeItem) tree.getRoot(); expandTree(root, false); - root.setExpanded(true); + root.setExpanded(false); }); textfieldDirectoryPath = new TextField(); diff --git a/src/main/java/org/jabref/gui/groups/GroupDialog.fxml b/src/main/java/org/jabref/gui/groups/GroupDialog.fxml index 5a4043cc930..75053c2749b 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialog.fxml +++ b/src/main/java/org/jabref/gui/groups/GroupDialog.fxml @@ -71,7 +71,7 @@ + text="%Searching for a keyword"> diff --git a/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java b/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java index e7a183f1d27..17d0607efce 100644 --- a/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupDialogViewModel.java @@ -170,20 +170,20 @@ private void setupValidation() { } }, ValidationMessage.error(String.format("%s > %n %s %n %n %s", - Localization.lang("Searching for keywords"), + Localization.lang("Searching for a keyword"), Localization.lang("Keywords"), Localization.lang("Invalid regular expression.")))); keywordFieldEmptyValidator = new FunctionBasedValidator<>( keywordGroupSearchFieldProperty, StringUtil::isNotBlank, - ValidationMessage.error(Localization.lang("Please enter a field name to search for keywords."))); + ValidationMessage.error(Localization.lang("Please enter a field name to search for a keyword."))); keywordSearchTermEmptyValidator = new FunctionBasedValidator<>( keywordGroupSearchTermProperty, input -> !StringUtil.isNullOrEmpty(input), ValidationMessage.error(String.format("%s > %n %s %n %n %s", - Localization.lang("Searching for keywords"), + Localization.lang("Searching for a keyword"), Localization.lang("Keywords"), Localization.lang("Search term is empty.") ))); diff --git a/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java b/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java index b2fa0f5221a..a3c93dfd898 100644 --- a/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java +++ b/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java @@ -172,6 +172,43 @@ public String expand(BibEntry bibentry, Character keywordDelimiter, BibDatabase public static String expandBrackets(String pattern, Character keywordDelimiter, BibEntry entry, BibDatabase database) { Objects.requireNonNull(pattern); Objects.requireNonNull(entry); + return expandBrackets(pattern, expandBracketContent(keywordDelimiter, entry, database)); + } + + /** + * Utility method creating a function taking the string representation of the content of a bracketed expression and + * expanding it. + * + * @param keywordDelimiter The keyword delimiter to use + * @param entry The {@link BibEntry} to use for expansion + * @param database The {@link BibDatabase} for field resolving. May be null. + * @return a function accepting a bracketed expression and returning the result of expanding it + */ + private static Function expandBracketContent(Character keywordDelimiter, BibEntry entry, BibDatabase database) { + return (String bracket) -> { + String expandedPattern; + List fieldParts = parseFieldAndModifiers(bracket); + // check whether there is a modifier on the end such as + // ":lower": + expandedPattern = getFieldValue(entry, fieldParts.get(0), keywordDelimiter, database); + if (fieldParts.size() > 1) { + // apply modifiers: + expandedPattern = applyModifiers(expandedPattern, fieldParts, 1); + } + return expandedPattern; + }; + } + + /** + * Expands a pattern. + * + * @param pattern The pattern to expand + * @param bracketContentHandler A function taking the string representation of the content of a bracketed pattern + * and expanding it + * @return The expanded pattern. Not null. + */ + public static String expandBrackets(String pattern, Function bracketContentHandler) { + Objects.requireNonNull(pattern); StringBuilder expandedPattern = new StringBuilder(); StringTokenizer parsedPattern = new StringTokenizer(pattern, "\\[]\"", true); @@ -181,23 +218,14 @@ public static String expandBrackets(String pattern, Character keywordDelimiter, case "\"" -> appendQuote(expandedPattern, parsedPattern); case "[" -> { String fieldMarker = contentBetweenBrackets(parsedPattern, pattern); - - List fieldParts = parseFieldMarker(fieldMarker); - // check whether there is a modifier on the end such as - // ":lower": - if (fieldParts.size() <= 1) { - expandedPattern.append(getFieldValue(entry, fieldMarker, keywordDelimiter, database)); - } else { - // apply modifiers: - String fieldValue = getFieldValue(entry, fieldParts.get(0), keywordDelimiter, database); - expandedPattern.append(applyModifiers(fieldValue, fieldParts, 1)); - } + expandedPattern.append(bracketContentHandler.apply(fieldMarker)); } case "\\" -> { if (parsedPattern.hasMoreTokens()) { expandedPattern.append(parsedPattern.nextToken()); + } else { + LOGGER.warn("Found a \"\\\" that is not part of an escape sequence"); } - // FIXME: else -> raise exception or log? (S.G.) } default -> expandedPattern.append(token); } @@ -1069,7 +1097,7 @@ public static String lastPage(String pages) { * @param arg The argument string. * @return An array of strings representing the parts of the marker */ - protected static List parseFieldMarker(String arg) { + protected static List parseFieldAndModifiers(String arg) { List parts = new ArrayList<>(); StringBuilder current = new StringBuilder(); boolean escaped = false; diff --git a/src/main/java/org/jabref/logic/citationkeypattern/CitationKeyGenerator.java b/src/main/java/org/jabref/logic/citationkeypattern/CitationKeyGenerator.java index 5d13cfa7484..616ba434e8b 100644 --- a/src/main/java/org/jabref/logic/citationkeypattern/CitationKeyGenerator.java +++ b/src/main/java/org/jabref/logic/citationkeypattern/CitationKeyGenerator.java @@ -1,11 +1,12 @@ package org.jabref.logic.citationkeypattern; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.function.Function; +import java.util.regex.PatternSyntaxException; import org.jabref.model.FieldChange; import org.jabref.model.database.BibDatabase; @@ -71,8 +72,7 @@ static String generateKey(BibEntry entry, String pattern, BibDatabase database) } /** - * Computes an appendix to a citation key that could make it unique. We use - * a-z for numbers 0-25, and then aa-az, ba-bz, etc. + * Computes an appendix to a citation key that could make it unique. We use a-z for numbers 0-25, and then aa-az, ba-bz, etc. * * @param number The appendix number. * @return The String to append. @@ -107,55 +107,31 @@ public static String cleanKey(String key, String unwantedCharacters) { return removeUnwantedCharacters(key, unwantedCharacters).replaceAll("\\s", ""); } + /** + * Generate a citation key for the given {@link BibEntry}. + * + * @param entry a {@link BibEntry} + * @return a citation key based on the user's preferences + */ public String generateKey(BibEntry entry) { - String key; - StringBuilder stringBuilder = new StringBuilder(); - try { - // get the type of entry - EntryType entryType = entry.getType(); - // Get the arrayList corresponding to the type - List typeList = new ArrayList<>(citeKeyPattern.getValue(entryType)); - if (!typeList.isEmpty()) { - typeList.remove(0); - } - boolean field = false; - for (String typeListEntry : typeList) { - if ("[".equals(typeListEntry)) { - field = true; - } else if ("]".equals(typeListEntry)) { - field = false; - } else if (field) { - // check whether there is a modifier on the end such as - // ":lower" - List parts = parseFieldMarker(typeListEntry); - Character delimiter = citationKeyPatternPreferences.getKeywordDelimiter(); - String pattern = "[" + parts.get(0) + "]"; - String label = removeUnwantedCharacters(expandBrackets(pattern, delimiter, entry, database), unwantedCharacters); - // apply modifier if present - if (parts.size() > 1) { - label = removeUnwantedCharacters(applyModifiers(label, parts, 1), unwantedCharacters); - } - // Remove all illegal characters from the label. - label = cleanKey(label, unwantedCharacters); - stringBuilder.append(label); - } else { - stringBuilder.append(typeListEntry); - } - } - } catch (Exception e) { - LOGGER.warn("Cannot make label", e); - } + Objects.requireNonNull(entry); + String currentKey = entry.getCitationKey().orElse(null); - key = stringBuilder.toString(); + String newKey = createCitationKeyFromPattern(entry); + newKey = replaceWithRegex(newKey); + newKey = appendLettersToKey(newKey, currentKey); - // Remove Regular Expressions while generating Keys - String regex = citationKeyPatternPreferences.getKeyPatternRegex(); - if ((regex != null) && !regex.trim().isEmpty()) { - String replacement = citationKeyPatternPreferences.getKeyPatternReplacement(); - key = key.replaceAll(regex, replacement); - } + return cleanKey(newKey, unwantedCharacters); + } - String oldKey = entry.getCitationKey().orElse(null); + /** + * A letter will be appended to the key based on the user's preferences, either always or to prevent duplicated keys. + * + * @param key the new key + * @param oldKey the old key + * @return a key, if needed, with an appended letter + */ + private String appendLettersToKey(String key, String oldKey) { long occurrences = database.getNumberOfCitationKeyOccurrences(key); if (Objects.equals(oldKey, key)) { @@ -165,14 +141,11 @@ public String generateKey(BibEntry entry) { boolean alwaysAddLetter = citationKeyPatternPreferences.getKeySuffix() == CitationKeyPatternPreferences.KeySuffix.ALWAYS; - boolean firstLetterA = citationKeyPatternPreferences.getKeySuffix() - == CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_A; - - String newKey; - if (!alwaysAddLetter && (occurrences == 0)) { - newKey = key; - } else { + if (alwaysAddLetter || occurrences != 0) { // The key is already in use, so we must modify it. + boolean firstLetterA = citationKeyPatternPreferences.getKeySuffix() + == CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_A; + int number = !alwaysAddLetter && !firstLetterA ? 1 : 0; String moddedKey; @@ -187,9 +160,64 @@ public String generateKey(BibEntry entry) { } } while (occurrences > 0); - newKey = moddedKey; + key = moddedKey; } - return newKey; + return key; + } + + /** + * Using preferences, replace matches to the provided regex with a string. + * + * @param key the citation key + * @return the citation key where matches to the regex are replaced + */ + private String replaceWithRegex(String key) { + // Remove Regular Expressions while generating Keys + String regex = citationKeyPatternPreferences.getKeyPatternRegex(); + if ((regex != null) && !regex.trim().isEmpty()) { + String replacement = citationKeyPatternPreferences.getKeyPatternReplacement(); + try { + key = key.replaceAll(regex, replacement); + } catch (PatternSyntaxException e) { + LOGGER.warn("There is a syntax error in the regular expression \"{}\" used to generate a citation key", regex, e); + } + } + return key; + } + + private String createCitationKeyFromPattern(BibEntry entry) { + // get the type of entry + EntryType entryType = entry.getType(); + // Get the arrayList corresponding to the type + List citationKeyPattern = citeKeyPattern.getValue(entryType); + if (citationKeyPattern.isEmpty()) { + return ""; + } + return expandBrackets(citationKeyPattern.get(0), expandBracketContent(entry)); + } + + /** + * A helper method to create a {@link Function} that takes a single bracketed expression, expands it, and cleans the key. + * + * @param entry the {@link BibEntry} that a citation key is generated for + * @return a cleaned citation key for the given {@link BibEntry} + */ + private Function expandBracketContent(BibEntry entry) { + Character keywordDelimiter = citationKeyPatternPreferences.getKeywordDelimiter(); + + return (String bracket) -> { + String expandedPattern; + List fieldParts = parseFieldAndModifiers(bracket); + + expandedPattern = removeUnwantedCharacters(getFieldValue(entry, fieldParts.get(0), keywordDelimiter, database), unwantedCharacters); + // check whether there is a modifier on the end such as + // ":lower": + if (fieldParts.size() > 1) { + // apply modifiers: + expandedPattern = applyModifiers(expandedPattern, fieldParts, 1); + } + return cleanKey(expandedPattern, unwantedCharacters); + }; } /** diff --git a/src/main/java/org/jabref/logic/importer/WebFetchers.java b/src/main/java/org/jabref/logic/importer/WebFetchers.java index 3d7c50fd87b..e2791d3c13d 100644 --- a/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -26,6 +26,7 @@ import org.jabref.logic.importer.fetcher.INSPIREFetcher; import org.jabref.logic.importer.fetcher.IacrEprintFetcher; import org.jabref.logic.importer.fetcher.IsbnFetcher; +import org.jabref.logic.importer.fetcher.JstorFetcher; import org.jabref.logic.importer.fetcher.LibraryOfCongress; import org.jabref.logic.importer.fetcher.MathSciNet; import org.jabref.logic.importer.fetcher.MedlineFetcher; @@ -104,6 +105,7 @@ public static SortedSet getSearchBasedFetchers(ImportFormatP set.add(new IEEE(importFormatPreferences)); set.add(new CompositeSearchBasedFetcher(set, 30)); set.add(new CollectionOfComputerScienceBibliographiesFetcher(importFormatPreferences)); + set.add(new JstorFetcher(importFormatPreferences)); return set; } @@ -166,6 +168,7 @@ public static Set getFullTextFetchers(ImportFormatPreferences i fetchers.add(new IEEE(importFormatPreferences)); fetchers.add(new ApsFetcher()); // Meta search + fetchers.add(new JstorFetcher(importFormatPreferences)); fetchers.add(new GoogleScholar(importFormatPreferences)); fetchers.add(new OpenAccessDoi()); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java new file mode 100644 index 00000000000..9f8cb7de04a --- /dev/null +++ b/src/main/java/org/jabref/logic/importer/fetcher/JstorFetcher.java @@ -0,0 +1,136 @@ +package org.jabref.logic.importer.fetcher; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.FulltextFetcher; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.ParseException; +import org.jabref.logic.importer.Parser; +import org.jabref.logic.importer.SearchBasedParserFetcher; +import org.jabref.logic.importer.fileformat.BibtexParser; +import org.jabref.logic.net.URLDownload; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.util.DummyFileUpdateMonitor; + +import org.apache.http.client.utils.URIBuilder; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; + +/** + * Fetcher for jstor.org + **/ +public class JstorFetcher implements SearchBasedParserFetcher, FulltextFetcher { + + private static final String HOST = "https://www.jstor.org"; + private static final String SEARCH_HOST = HOST + "/open/search"; + private static final String CITE_HOST = HOST + "/citation/text"; + + private final ImportFormatPreferences importFormatPreferences; + + public JstorFetcher(ImportFormatPreferences importFormatPreferences) { + this.importFormatPreferences = importFormatPreferences; + } + + @Override + public URL getURLForQuery(String query) throws URISyntaxException, MalformedURLException { + URIBuilder uriBuilder = new URIBuilder(SEARCH_HOST); + uriBuilder.addParameter("Query", query); + return uriBuilder.build().toURL(); + } + + @Override + public URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URISyntaxException, MalformedURLException, FetcherException { + URIBuilder uriBuilder = new URIBuilder(SEARCH_HOST); + StringBuilder stringBuilder = new StringBuilder(); + if (!complexSearchQuery.getDefaultFieldPhrases().isEmpty()) { + stringBuilder.append(complexSearchQuery.getDefaultFieldPhrases()); + } + if (!complexSearchQuery.getAuthors().isEmpty()) { + for (String author : complexSearchQuery.getAuthors()) { + stringBuilder.append("au:").append(author); + } + } + if (!complexSearchQuery.getTitlePhrases().isEmpty()) { + for (String title : complexSearchQuery.getTitlePhrases()) { + stringBuilder.append("ti:").append(title); + } + } + if (complexSearchQuery.getJournal().isPresent()) { + stringBuilder.append("pt:").append(complexSearchQuery.getJournal().get()); + } + if (complexSearchQuery.getSingleYear().isPresent()) { + uriBuilder.addParameter("sd", String.valueOf(complexSearchQuery.getSingleYear().get())); + uriBuilder.addParameter("ed", String.valueOf(complexSearchQuery.getSingleYear().get())); + } + if (complexSearchQuery.getFromYear().isPresent()) { + uriBuilder.addParameter("sd", String.valueOf(complexSearchQuery.getFromYear().get())); + } + if (complexSearchQuery.getToYear().isPresent()) { + uriBuilder.addParameter("ed", String.valueOf(complexSearchQuery.getToYear().get())); + } + + uriBuilder.addParameter("Query", stringBuilder.toString()); + return uriBuilder.build().toURL(); + } + + @Override + public Parser getParser() { + return inputStream -> { + List entries; + try { + Document doc = Jsoup.parse(inputStream, null, HOST); + List elements = doc.body().getElementsByClass("cite-this-item"); + StringBuilder stringBuilder = new StringBuilder(); + for (Element element : elements) { + String id = element.attr("href").replace("citation/info/", ""); + + String data = new URLDownload(CITE_HOST + id).asString(); + stringBuilder.append(data); + } + BibtexParser parser = new BibtexParser(importFormatPreferences, new DummyFileUpdateMonitor()); + entries = new ArrayList<>(parser.parseEntries(stringBuilder.toString())); + } catch (IOException e) { + throw new ParseException("Could not download data from jstor.org", e); + } + return entries; + }; + } + + @Override + public String getName() { + return "JSTOR"; + } + + @Override + public Optional findFullText(BibEntry entry) throws IOException, FetcherException { + if (entry.getField(StandardField.URL).isEmpty()) { + return Optional.empty(); + } + + String page = new URLDownload(entry.getField(StandardField.URL).get()).asString(); + + Document doc = Jsoup.parse(page); + + List elements = doc.getElementsByAttribute("data-doi"); + if (elements.size() != 1) { + return Optional.empty(); + } + + String url = elements.get(0).attr("href"); + return Optional.of(new URL(url)); + } + + @Override + public TrustLevel getTrustLevel() { + return TrustLevel.META_SEARCH; + } +} diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 4bf0b2a16ba..f3e6517cce9 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -564,7 +564,7 @@ private JabRefPreferences() { defaults.put(OO_JARS_PATH, OpenOfficePreferences.DEFAULT_LINUX_PATH); } - defaults.put(OO_SYNC_WHEN_CITING, Boolean.FALSE); + defaults.put(OO_SYNC_WHEN_CITING, Boolean.TRUE); defaults.put(OO_SHOW_PANEL, Boolean.FALSE); defaults.put(OO_USE_ALL_OPEN_BASES, Boolean.TRUE); defaults.put(OO_BIBLIOGRAPHY_STYLE_FILE, StyleLoader.DEFAULT_AUTHORYEAR_STYLE_PATH); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 89abfa2418c..2d2bacc4113 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2054,7 +2054,7 @@ Intersection=Intersection Union=Union Collect\ by=Collect by Explicit\ selection=Explicit selection -Searching\ for\ keywords=Searching for keywords +Searching\ for\ a\ keyword=Searching for a keyword Free\ search\ expression=Free search expression Specified\ keywords=Specified keywords Cited\ entries=Cited entries @@ -2098,7 +2098,7 @@ Unable\ to\ open\ ShortScience.=Unable to open ShortScience. Shared\ database=Shared database Lookup=Lookup -Please\ enter\ a\ field\ name\ to\ search\ for\ keywords.=Please enter a field name to search for keywords. +Please\ enter\ a\ field\ name\ to\ search\ for\ a\ keyword.=Please enter a field name to search for a keyword. Access\ date\ of\ the\ address\ specified\ in\ the\ url\ field.=Access date of the address specified in the url field. Additional\ information\ related\ to\ the\ resource\ indicated\ by\ the\ eprint\ field.=Additional information related to the resource indicated by the eprint field. Annex\ to\ the\ eventtitle\ field.=Annex to the eventtitle field. diff --git a/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java b/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java index f5931a8f1b0..c7899d32c0b 100644 --- a/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java +++ b/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java @@ -278,4 +278,18 @@ void expandBracketsWithAuthorStartingWithBrackets() { .withField(StandardField.AUTHOR, "Patrik {\\v{S}}pan{\\v{e}}l and Kseniya Dryahina and David Smith"); assertEquals("ŠpanělEtAl", BracketedPattern.expandBrackets("[authEtAl:latex_to_unicode]", null, bibEntry, null)); } + + @Test + void expandBracketsWithModifierContainingRegexCharacterCkass() { + BibEntry bibEntry = new BibEntry().withField(StandardField.TITLE, "Wickedness:Managing"); + + assertEquals("Wickedness.Managing", BracketedPattern.expandBrackets("[title:regex(\"[:]+\",\".\")]", null, bibEntry, null)); + } + + @Test + void expandBracketsEmptyStringFromEmptyBrackets() { + BibEntry bibEntry = new BibEntry(); + + assertEquals("", BracketedPattern.expandBrackets("[]", null, bibEntry, null)); + } } diff --git a/src/test/java/org/jabref/logic/citationkeypattern/CitationKeyGeneratorTest.java b/src/test/java/org/jabref/logic/citationkeypattern/CitationKeyGeneratorTest.java index e31a0776772..23eb958cb2c 100644 --- a/src/test/java/org/jabref/logic/citationkeypattern/CitationKeyGeneratorTest.java +++ b/src/test/java/org/jabref/logic/citationkeypattern/CitationKeyGeneratorTest.java @@ -1,6 +1,5 @@ package org.jabref.logic.citationkeypattern; -import java.util.Collections; import java.util.Optional; import org.jabref.logic.importer.ImportFormatPreferences; @@ -80,8 +79,7 @@ static String generateKey(BibEntry entry, String pattern) { } static String generateKey(BibEntry entry, String pattern, BibDatabase database) { - GlobalCitationKeyPattern keyPattern = new GlobalCitationKeyPattern(Collections.emptyList()); - keyPattern.setDefaultValue(pattern); + GlobalCitationKeyPattern keyPattern = GlobalCitationKeyPattern.fromPattern(pattern); CitationKeyPatternPreferences patternPreferences = new CitationKeyPatternPreferences( false, false, @@ -1070,7 +1068,7 @@ void generateKeyWithMinusInCitationStyleOutsideAField() { .withField(StandardField.AUTHOR, AUTHOR_STRING_FIRSTNAME_FULL_LASTNAME_FULL_COUNT_1) .withField(StandardField.YEAR, "2019"); - assertEquals("Newton-2019", generateKey(entry, "[auth]-[year]")); + assertEquals("Newton2019", generateKey(entry, "[auth]-[year]")); } @Test @@ -1078,7 +1076,7 @@ void generateKeyWithWithFirstNCharacters() { BibEntry entry = new BibEntry().withField(StandardField.AUTHOR, "Newton, Isaac") .withField(StandardField.YEAR, "2019"); - assertEquals("newt-2019", generateKey(entry, "[auth4:lower]-[year]")); + assertEquals("newt2019", generateKey(entry, "[auth4:lower]-[year]")); } @Test @@ -1101,4 +1099,31 @@ void generateKeyWithNonNormalizedUnicode() { assertEquals("Modele", generateKey(bibEntry, "[veryshorttitle]")); } + + @Test + void generateKeyWithModifierContainingRegexCharacterClass() { + BibEntry bibEntry = new BibEntry().withField(StandardField.TITLE, "Wickedness Managing"); + + assertEquals("WM", generateKey(bibEntry, "[title:regex(\"[a-z]+\",\"\")]")); + } + + @Test + void generateKeyDoesNotModifyTheKeyWithIncorrectRegexReplacement() { + String pattern = "[title]"; + GlobalCitationKeyPattern keyPattern = GlobalCitationKeyPattern.fromPattern(pattern); + CitationKeyPatternPreferences patternPreferences = new CitationKeyPatternPreferences( + false, + false, + false, + CitationKeyPatternPreferences.KeySuffix.SECOND_WITH_A, + "[", // Invalid regexp + "", + DEFAULT_UNWANTED_CHARACTERS, + keyPattern, + ','); + + BibEntry bibEntry = new BibEntry().withField(StandardField.TITLE, "Wickedness Managing"); + assertEquals("WickednessManaging", + new CitationKeyGenerator(keyPattern, new BibDatabase(), patternPreferences).generateKey(bibEntry)); + } } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java new file mode 100644 index 00000000000..a55f38def16 --- /dev/null +++ b/src/test/java/org/jabref/logic/importer/fetcher/JstorFetcherTest.java @@ -0,0 +1,81 @@ +package org.jabref.logic.importer.fetcher; + +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.SearchBasedFetcher; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.testutils.category.FetcherTest; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.mockito.Answers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +@FetcherTest +public class JstorFetcherTest implements SearchBasedFetcherCapabilityTest { + + private final JstorFetcher fetcher = new JstorFetcher(mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS)); + + private final BibEntry bibEntry = new BibEntry(StandardEntryType.Article) + .withCitationKey("10.2307/90002164") + .withField(StandardField.AUTHOR, "Yang Yanxia") + .withField(StandardField.TITLE, "Test Anxiety Analysis of Chinese College Students in Computer-based Spoken English Test") + .withField(StandardField.ISSN, "11763647, 14364522") + .withField(StandardField.JOURNAL, "Journal of Educational Technology & Society") + .withField(StandardField.ABSTRACT, "ABSTRACT Test anxiety was a commonly known or assumed factor that could greatly influence performance of test takers. With the employment of designed questionnaires and computer-based spoken English test, this paper explored test anxiety manifestation of Chinese college students from both macro and micro aspects, and found out that the major anxiety in computer-based spoken English test was spoken English test anxiety, which consisted of test anxiety and communication apprehension. Regard to proximal test anxiety, the causes listed in proper order as low spoken English abilities, lack of speaking techniques, anxiety from the evaluative process and inadaptability with computer-based spoken English test format. As to distal anxiety causes, attitude toward learning spoken English and self-evaluation of speaking abilities were significantly negatively correlated with test anxiety. Besides, as test anxiety significantly associated often with test performance, a look at pedagogical implications has been discussed in this paper.") + .withField(StandardField.PUBLISHER, "International Forum of Educational Technology & Society") + .withField(StandardField.NUMBER, "2") + .withField(StandardField.PAGES, "63--73") + .withField(StandardField.VOLUME, "20") + .withField(StandardField.URL, "http://www.jstor.org/stable/90002164") + .withField(StandardField.YEAR, "2017"); + + @Test + void searchByTitle() throws Exception { + List entries = fetcher.performSearch("ti: \"Test Anxiety Analysis of Chinese College Students in Computer-based Spoken English Test\""); + assertEquals(Collections.singletonList(bibEntry), entries); + } + + @Test + void fetchPDF() throws IOException, FetcherException { + Optional url = fetcher.findFullText(bibEntry); + assertEquals(Optional.of(new URL("https://www.jstor.org/stable/pdf/90002164.pdf")), url); + } + + @Override + public SearchBasedFetcher getFetcher() { + return fetcher; + } + + @Override + public List getTestAuthors() { + return List.of("Haman", "Medlin"); + } + + @Override + public String getTestJournal() { + return "Test"; + } + + @Disabled("jstor does not support search only based on year") + @Override + public void supportsYearRangeSearch() throws Exception { + + } + + @Disabled("jstor does not support search only based on year") + @Override + public void supportsYearSearch() throws Exception { + + } +}