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

Change copy-paste function to handle string constants (follow up PR) #11037

Merged
merged 32 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a0d6f72
[Copy] Include string constants in copy (#11)
andersblomqvist Mar 1, 2024
5e98a05
[Copy] New method for serializing string constants (#12)
andersblomqvist Mar 1, 2024
4dc86bb
Add a sanity check for null for clipboard content
JXNCTED Mar 1, 2024
765cb0e
[Fix] Add parsed serilization when save settings
JXNCTED Mar 1, 2024
ea946c4
Merge pull request #14 from DD2480-Group1/issue-13/null-when-copying
andersblomqvist Mar 2, 2024
5129ae9
feat: import string constants when pasting #9
hanstig Mar 3, 2024
dd821b9
feat: Add string constant validity checker and dialog messages #9
hanstig Mar 3, 2024
404e781
[Copy] Copy referenced constant strings to clipboard (#16)
JXNCTED Mar 4, 2024
e49562f
Merge pull request #19 from DD2480-Group1/paste-function-revert
andersblomqvist Mar 4, 2024
7485e16
feat: new unit tests
real-darth Mar 4, 2024
1e69400
Merge branch 'JabRef:main' into fix-for-issue-10872
andersblomqvist Mar 5, 2024
997537a
Merge remote-tracking branch 'origin/main' into fix-for-issue-10872
JXNCTED Mar 7, 2024
c00ee33
Update CHANGELOG with copy and paste function
JXNCTED Mar 7, 2024
7a53d8a
Fix Checkstyle failing by reformat the code
JXNCTED Mar 7, 2024
bae8338
Fix OpenRewrite failing by running rewriteRun
JXNCTED Mar 7, 2024
c3ffd8b
Refactor by extract methods in setContent
JXNCTED Mar 7, 2024
053b25e
Merge branch 'main' into fix-for-issue-10872
JXNCTED Mar 10, 2024
047828d
Merge remote-tracking branch 'upstream/main' into fix-for-issue-10872
Siedlerchr Mar 17, 2024
1030d34
collet failures
Siedlerchr Mar 17, 2024
15746c0
changelog and use os.newline
Siedlerchr Mar 17, 2024
f58283c
checkstyle
Siedlerchr Mar 17, 2024
7244e8b
use real bibentrytypes manager
Siedlerchr Mar 17, 2024
404e4a2
Fix CHANGELOG.md
koppor Mar 18, 2024
8b4220a
Swap if branches
koppor Mar 18, 2024
fa28472
Code cleanup
koppor Mar 18, 2024
ccc0a84
Use List for getUsedStringValues
koppor Mar 18, 2024
4e104a5
Merge remote-tracking branch 'origin/main' into fix-for-issue-10872
koppor Mar 18, 2024
e81d5c0
Fix submodule
koppor Mar 18, 2024
40d6f02
Collection is better
koppor Mar 18, 2024
abefd5f
Fix csl-styles
koppor Mar 18, 2024
b0b7e90
Remove empty line
koppor Mar 18, 2024
dcef84e
Group BibTeX string l10n together
koppor Mar 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added the ability to zoom in and out in the document viewer using <kbd>Ctrl</kbd> + <kbd>Scroll</kbd>. [#10964](https://github.com/JabRef/jabref/pull/10964)
- We added a Cleanup for removing non-existent files and grouped the related options [#10929](https://github.com/JabRef/jabref/issues/10929)
- We added the functionality to parse the bibliography of PDFs using the GROBID online service. [#10200](https://github.com/JabRef/jabref/issues/10200)
- We added support for BibTeX String constants during copy & paste between libraries [#10872](https://github.com/JabRef/jabref/issues/10872)

### Changed

Expand All @@ -47,6 +48,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We enhanced the dialog for adding new fields in the content selector with a selection box containing a list of standard fields. [#10912](https://github.com/JabRef/jabref/pull/10912)
- We store the citation relations in an LRU cache to avoid bloating the memory and out-of-memory exceptions. [#10958](https://github.com/JabRef/jabref/issues/10958)
- Keywords filed are now displayed as tags. [#10910](https://github.com/JabRef/jabref/pull/10910)
- When pasting, string constants are automatically added to the library database, while referenced constants are added to the clipboard during copying. [#10872](https://github.com/JabRef/jabref/issues/10872)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why two entries in the changelog?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One for changed behavior and one for new feature


### Fixed

Expand Down
22 changes: 16 additions & 6 deletions src/main/java/org/jabref/gui/ClipBoardManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.jabref.model.database.BibDatabaseMode;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.entry.BibtexString;
import org.jabref.preferences.PreferencesService;

import org.slf4j.Logger;
Expand Down Expand Up @@ -155,14 +156,23 @@ public void setContent(String string) {
}

public void setContent(List<BibEntry> entries, BibEntryTypesManager entryTypesManager) throws IOException {
final ClipboardContent content = new ClipboardContent();
BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferencesService.getFieldPreferences()), entryTypesManager);
String serializedEntries = writer.serializeAll(entries, BibDatabaseMode.BIBTEX);
String serializedEntries = serializeEntries(entries, entryTypesManager);
setContent(serializedEntries);
}

public void setContent(List<BibEntry> entries, BibEntryTypesManager entryTypesManager, List<BibtexString> stringConstants) throws IOException {
StringBuilder builder = new StringBuilder();
stringConstants.forEach(strConst -> builder.append(strConst.getParsedSerialization() == null ? "" : strConst.getParsedSerialization()));
String serializedEntries = serializeEntries(entries, entryTypesManager);
builder.append(serializedEntries);
setContent(builder.toString());
}

private String serializeEntries(List<BibEntry> entries, BibEntryTypesManager entryTypesManager) throws IOException {
// BibEntry is not Java serializable. Thus, we need to do the serialization manually
// At reading of the clipboard in JabRef, we parse the plain string in all cases, so we don't need to flag we put BibEntries here
// Furthermore, storing a string also enables other applications to work with the data
content.putString(serializedEntries);
clipboard.setContent(content);
setPrimaryClipboardContent(content);
BibEntryWriter writer = new BibEntryWriter(new FieldWriter(preferencesService.getFieldPreferences()), entryTypesManager);
return writer.serializeAll(entries, BibDatabaseMode.BIBTEX);
}
}
30 changes: 28 additions & 2 deletions src/main/java/org/jabref/gui/externalfiles/ImportHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.jabref.gui.StateManager;
import org.jabref.gui.duplicationFinder.DuplicateResolverDialog;
import org.jabref.gui.fieldeditors.LinkedFileViewModel;
import org.jabref.gui.libraryproperties.constants.ConstantsItemModel;
import org.jabref.gui.undo.UndoableInsertEntries;
import org.jabref.gui.util.BackgroundTask;
import org.jabref.gui.util.DefaultTaskExecutor;
Expand All @@ -40,7 +41,9 @@
import org.jabref.logic.util.io.FileUtil;
import org.jabref.model.FieldChange;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.database.KeyCollisionException;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibtexString;
import org.jabref.model.entry.LinkedFile;
import org.jabref.model.entry.field.StandardField;
import org.jabref.model.entry.identifier.ArXivIdentifier;
Expand Down Expand Up @@ -311,13 +314,36 @@ private void generateKeys(List<BibEntry> entries) {
public List<BibEntry> handleBibTeXData(String entries) {
BibtexParser parser = new BibtexParser(preferencesService.getImportFormatPreferences(), fileUpdateMonitor);
try {
return parser.parseEntries(new ByteArrayInputStream(entries.getBytes(StandardCharsets.UTF_8)));
List<BibEntry> result = parser.parseEntries(new ByteArrayInputStream(entries.getBytes(StandardCharsets.UTF_8)));
List<BibtexString> stringConstants = parser.getStringValues();
importStringConstantsWithDuplicateCheck(stringConstants);
return result;
} catch (ParseException ex) {
LOGGER.error("Could not paste", ex);
return Collections.emptyList();
}
}

public void importStringConstantsWithDuplicateCheck(List<BibtexString> stringConstants) {
List<String> failures = new ArrayList<>();

for (BibtexString stringConstantToAdd : stringConstants) {
try {
ConstantsItemModel checker = new ConstantsItemModel(stringConstantToAdd.getName(), stringConstantToAdd.getContent());
if (checker.combinedValidationValidProperty().get()) {
bibDatabaseContext.getDatabase().addString(stringConstantToAdd);
} else {
failures.add(Localization.lang("String constant \"%0\" was not imported because it is not a valid string constant", stringConstantToAdd.getName()));
}
} catch (KeyCollisionException ex) {
failures.add(Localization.lang("String constant %0 was not imported because it already exists in this library", stringConstantToAdd.getName()));
}
}
if (!failures.isEmpty()) {
dialogService.showWarningDialogAndWait(Localization.lang("Importing String constants"), Localization.lang("Could not import the following string constants:\n %0", String.join("\n", failures)));
}
}

public List<BibEntry> handleStringData(String data) throws FetcherException {
if ((data == null) || data.isEmpty()) {
return Collections.emptyList();
Expand Down Expand Up @@ -356,7 +382,7 @@ private List<BibEntry> tryImportFormats(String data) {
}

private List<BibEntry> fetchByDOI(DOI doi) throws FetcherException {
LOGGER.info("Found DOI identifer in clipboard");
LOGGER.info("Found DOI identifier in clipboard");
Optional<BibEntry> entry = new DoiFetcher(preferencesService.getImportFormatPreferences()).performSearchById(doi.getDOI());
return OptionalUtil.toList(entry);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package org.jabref.gui.libraryproperties.constants;

import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
Expand All @@ -18,6 +18,7 @@
import org.jabref.gui.libraryproperties.PropertiesTabViewModel;
import org.jabref.logic.bibtex.comparator.BibtexStringComparator;
import org.jabref.logic.help.HelpFile;
import org.jabref.logic.util.OS;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibtexString;
import org.jabref.preferences.FilePreferences;
Expand Down Expand Up @@ -86,9 +87,12 @@ private ConstantsItemModel convertFromBibTexString(BibtexString bibtexString) {

@Override
public void storeSettings() {
databaseContext.getDatabase().setStrings(stringsListProperty.stream()
.map(this::fromBibtexStringViewModel)
.collect(Collectors.toList()));
List<BibtexString> strings = stringsListProperty.stream()
.map(this::fromBibtexStringViewModel)
.toList();
strings.forEach(string -> string.setParsedSerialization("@String{" +
string.getName() + " = " + string.getContent() + "}" + OS.NEWLINE));
databaseContext.getDatabase().setStrings(strings);
}

private BibtexString fromBibtexStringViewModel(ConstantsItemModel viewModel) {
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/org/jabref/gui/maintable/MainTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.jabref.model.database.event.EntriesAddedEvent;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.entry.BibtexString;
import org.jabref.model.util.FileUpdateMonitor;
import org.jabref.preferences.PreferencesService;

Expand Down Expand Up @@ -257,8 +258,13 @@ public void copy() {
List<BibEntry> selectedEntries = getSelectedEntries();

if (!selectedEntries.isEmpty()) {
List<BibtexString> stringConstants = getUsedStringValues(selectedEntries);
try {
clipBoardManager.setContent(selectedEntries, entryTypesManager);
if (!stringConstants.isEmpty()) {
clipBoardManager.setContent(selectedEntries, entryTypesManager, stringConstants);
} else {
clipBoardManager.setContent(selectedEntries, entryTypesManager);
}
dialogService.notify(Localization.lang("Copied %0 entry(ies)", selectedEntries.size()));
} catch (IOException e) {
LOGGER.error("Error while copying selected entries to clipboard.", e);
Expand Down Expand Up @@ -489,4 +495,8 @@ private Optional<BibEntryTableViewModel> findEntry(BibEntry entry) {
.filter(viewModel -> viewModel.getEntry().equals(entry))
.findFirst();
}

private List<BibtexString> getUsedStringValues(List<BibEntry> entries) {
return database.getDatabase().getUsedStrings(entries).stream().toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ public List<BibEntry> parseEntries(InputStream inputStream) throws ParseExceptio
}
}

public List<BibtexString> getStringValues() {
return database.getStringValues().stream().toList();
}

public Optional<BibEntry> parseSingleEntry(String bibtexString) throws ParseException {
return parseEntries(bibtexString).stream().findFirst();
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/jabref/model/entry/BibtexString.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ public String getUserComments() {
public Object clone() {
BibtexString clone = new BibtexString(name, content);
clone.setId(id);
if (parsedSerialization != null) {
clone.setParsedSerialization(parsedSerialization);
}

return clone;
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/csl-styles
Submodule csl-styles updated 35 files
+4 −4 .github/workflows/merge.yaml
+2 −2 .github/workflows/sheldon.yaml
+5 −5 Gemfile.lock
+6 −11 american-political-science-association.csl
+2 −2 american-sociological-association.csl
+152 −321 apa-annotated-bibliography.csl
+124 −292 apa-cv.csl
+152 −321 apa-no-ampersand.csl
+152 −321 apa-no-initials.csl
+384 −241 apa-numeric-superscript-brackets.csl
+384 −241 apa-numeric-superscript.csl
+152 −321 apa-single-spaced.csl
+152 −321 apa-with-abstract.csl
+158 −320 apa.csl
+4 −3 association-for-computing-machinery.csl
+103 −72 ausonius-editions.csl
+5 −21 bern-university-of-applied-sciences-school-of-agricultural-forest-and-food-sciences-hafl.csl
+52 −37 cambridge-university-press-author-date-cambridge-a.csl
+3 −14 deutsche-gesellschaft-fur-psychologie.csl
+30 −55 food-and-agriculture-organization-of-the-united-nations-numeric.csl
+50 −107 food-and-agriculture-organization-of-the-united-nations.csl
+19 −28 frontiers.csl
+12 −12 harvard-leeds-beckett-university.csl
+2 −9 harvard-university-of-bath.csl
+28 −35 harvard-university-of-the-west-of-england.csl
+1 −2 heiliger-dienst.csl
+2 −1 howard-hughes-medical-institute.csl
+88 −87 journal-of-applied-polymer-science.csl
+41 −36 metropolitiques.csl
+45 −77 the-plant-cell.csl
+0 −1,433 ugent-geschiedenis-nederlands.csl
+2 −2 universitat-wurzburg-institut-fur-deutsche-philologie-museologie.csl
+1 −0 utah-geological-survey.csl
+26 −29 who-europe-harvard.csl
+31 −34 who-europe-numeric.csl
6 changes: 6 additions & 0 deletions src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2646,3 +2646,9 @@ Source\ URL=Source URL
Redownload\ file=Redownload file
Redownload\ missing\ files=Redownload missing files
Redownload\ missing\ files\ for\ current\ library?=Redownload missing files for current library?

String\ constant\ "%0"\ was\ not\ imported\ because\ it\ is\ not\ a\ valid\ string\ constant=String constant "%0" was not imported because it is not a valid string constant
String\ constant\ %0\ was\ not\ imported\ because\ it\ already\ exists\ in\ this\ library=String constant %0 was not imported because it already exists in this library
Could\ not\ import\ the\ following\ string\ constants\:\n\ %0=Could not import the following string constants:\n %0
Importing\ String\ constants=Importing String constants

128 changes: 128 additions & 0 deletions src/test/java/org/jabref/gui/ClipBoardManagerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package org.jabref.gui;

import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.input.Clipboard;

import org.jabref.architecture.AllowedToUseAwt;
import org.jabref.logic.bibtex.FieldPreferences;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.entry.BibtexString;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.StandardField;
import org.jabref.model.entry.types.StandardEntryType;
import org.jabref.preferences.PreferencesService;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@AllowedToUseAwt("Requires AWT for clipboard access")
public class ClipBoardManagerTest {

private BibEntryTypesManager entryTypesManager;
private ClipBoardManager clipBoardManager;

@BeforeEach
void setUp() {
// create preference service mock
PreferencesService preferencesService = mock(PreferencesService.class);
FieldPreferences fieldPreferences = mock(FieldPreferences.class);
List<Field> fields = List.of(StandardField.URL);
ObservableList<Field> nonWrappableFields = FXCollections.observableArrayList(fields);
// set up mock behaviours for preferences service
when(fieldPreferences.getNonWrappableFields()).thenReturn(nonWrappableFields);
when(preferencesService.getFieldPreferences()).thenReturn(fieldPreferences);

// create mock clipboard
Clipboard clipboard = mock(Clipboard.class);
// create primary clipboard and set a temporary value
StringSelection selection = new StringSelection("test");
java.awt.datatransfer.Clipboard clipboardPrimary = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboardPrimary.setContents(selection, selection);

// create mock entry manager and set up behaviour for mock
entryTypesManager = new BibEntryTypesManager();

// initialize a clipBoardManager
clipBoardManager = new ClipBoardManager(clipboard, clipboardPrimary, preferencesService);
}

@DisplayName("Check that the ClipBoardManager can set a bibentry as its content from the clipboard")
@Test
void copyStringBibEntry() throws IOException {
// Arrange
String expected = "@Article{,\n author = {Claudepierre, S. G.},\n journal = {IEEE},\n}";

// create BibEntry
BibEntry bibEntry = new BibEntry();
// construct an entry
bibEntry.setType(StandardEntryType.Article);
bibEntry.setField(StandardField.JOURNAL, "IEEE");
bibEntry.setField(StandardField.AUTHOR, "Claudepierre, S. G.");
// add entry to list
List<BibEntry> bibEntries = new ArrayList<>();
bibEntries.add(bibEntry);

// Act
clipBoardManager.setContent(bibEntries, entryTypesManager);

// Assert
String actual = ClipBoardManager.getContentsPrimary();
// clean strings
actual = actual.replaceAll("\\s+", " ").trim();
expected = expected.replaceAll("\\s+", " ").trim();

assertEquals(expected, actual);
}

@Test
@DisplayName("Check that the ClipBoardManager can handle a bibentry with string constants correctly from the clipboard")
void copyStringBibEntryWithStringConstants() throws IOException {
// Arrange
String expected = "@String{grl = \"Geophys. Res. Lett.\"}@Article{,\n" + " author = {Claudepierre, S. G.},\n" +
" journal = {grl},\n" + "}";
// create BibEntry
BibEntry bibEntry = new BibEntry();
// construct an entry
bibEntry.setType(StandardEntryType.Article);
bibEntry.setField(StandardField.JOURNAL, "grl");
bibEntry.setField(StandardField.AUTHOR, "Claudepierre, S. G.");
// add entry to list
List<BibEntry> bibEntries = new ArrayList<>();
bibEntries.add(bibEntry);

// string constants
List<BibtexString> constants = new ArrayList<>();

// Mock BibtexString
BibtexString bibtexString = mock(BibtexString.class);

// define return value for getParsedSerialization()
when(bibtexString.getParsedSerialization()).thenReturn("@String{grl = \"Geophys. Res. Lett.\"}");
// add the constant
constants.add(bibtexString);

// Act
clipBoardManager.setContent(bibEntries, entryTypesManager, constants);

// Assert
String actual = ClipBoardManager.getContentsPrimary();
// clean strings
actual = actual.replaceAll("\\s+", " ").trim();
expected = expected.replaceAll("\\s+", " ").trim();

assertEquals(expected, actual);
}
}
Loading
Loading