From 76f0063427299e2b7548a28a0e087948c2e42fd8 Mon Sep 17 00:00:00 2001 From: Christoph Date: Mon, 25 Jul 2022 14:41:43 +0200 Subject: [PATCH 01/31] New Crowdin updates (#8995) --- src/main/resources/l10n/JabRef_fr.properties | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 46020ee888c..f65aa14b90a 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -19,7 +19,9 @@ Unable\ to\ monitor\ file\ changes.\ Please\ close\ files\ and\ processes\ and\ %0/%1\ entries=%0/%1 entrées +Export\ operation\ finished\ successfully.=L'opération d'export s'est terminée avec succès. +Reveal\ in\ File\ Explorer=Montrer dans l'explorateur de fichiers %0\ matches\ the\ regular\ expression\ %1=%0 correspond à l'expression régulière %1 @@ -703,16 +705,23 @@ Remove\ group=Supprimer le groupe Remove\ group\ and\ subgroups=Supprimer le groupe et les sous-groupes +Remove\ groups\ and\ subgroups=Supprimer les groupes et les sous-groupes +Remove\ all\ selected\ groups\ and\ keep\ their\ subgroups?=Supprimer tous les groupes sélectionnés et conserver leurs sous-groupes ? +Remove\ group\ "%0"\ and\ keep\ its\ subgroups?=Supprimer le groupe « %0 » et conserver ses sous-groupes ? +Remove\ groups=Supprimer les groupes +Removed\ all\ selected\ groups.=Tous les groupes sélectionnés ont été supprimés. Remove\ group\ "%0"\ and\ its\ subgroups?=Supprimer le groupe « %0 » et ses sous-groupes ? Removed\ group\ "%0"\ and\ its\ subgroups.=Groupe « %0 » et ses sous-groupes supprimés. +Remove\ all\ selected\ groups\ and\ their\ subgroups?=Supprimer tous les groupes sélectionnés et leurs sous-groupes ? +Removed\ all\ selected\ groups\ and\ their\ subgroups.=Tous les groupes sélectionnés et leurs sous-groupes ont été supprimés. Remove\ link=Supprimer le lien @@ -825,6 +834,12 @@ Size=Taille Skipped\ -\ No\ PDF\ linked=Sauté - Pas de PDF lié Skipped\ -\ PDF\ does\ not\ exist=Omis - Le PDF n'existe pas +JabRef\ skipped\ the\ entry.=JabRef a ignoré l'entrée. +Import\ error=Erreur d'importation +Open\ library\ error=Erreur d'ouverture du fichier +Please\ check\ your\ library\ file\ for\ wrong\ syntax.=Veuillez vérifier la syntaxe de votre fichier bibliographique. +SourceTab\ error=Erreur de SourceTab +User\ input\ via\ entry-editor\ in\ `{}bibtex\ source`\ tab\ led\ to\ failure.=L'entrée de l'utilisateur via l'éditeur d'entrée dans l'onglet `{}bibtex source` a conduit à un échec. Sort\ subgroups=Trier les sous-groupes @@ -1244,6 +1259,7 @@ Connection\ failed\!=Échec de la connexion \! Connection\ successful\!=Connexion réussie \! SSL\ Configuration=Configuration SSL +SSL\ configuration\ changed=Configuration SSL modifiée SSL\ certificate\ file=Fichier de certificat SSL Duplicate\ Certificates=Dupliquer les certificats You\ already\ added\ this\ certificate=Vous avez déjà ajouté ce certificat @@ -1283,6 +1299,7 @@ Please\ open\ %0\ manually.=Veuillez ouvrir manuellement %0 . The\ link\ has\ been\ copied\ to\ the\ clipboard.=Le lien a été copié dans le presse-papiers. Open\ %0\ file=Ouvrir le fichier %0 +Could\ not\ detect\ terminal\ automatically.\ Please\ define\ a\ custom\ terminal\ in\ the\ preferences.=Impossible de détecter le terminal automatiquement. Veuillez définir un terminal personnalisé dans les préférences. Cannot\ delete\ file=Le fichier ne peut pas être supprimé File\ permission\ error=Erreur due aux permissions du fichier @@ -1670,6 +1687,8 @@ Issue\ report\ successful=Signalement de l'anomalie réussi Your\ issue\ was\ reported\ in\ your\ browser.=Votre anomalie a été affichée dans votre navigateur. The\ log\ and\ exception\ information\ was\ copied\ to\ your\ clipboard.=Le journal et les informations d'anomalie ont été copiées dans votre presse-papier. Please\ paste\ this\ information\ (with\ Ctrl+V)\ in\ the\ issue\ description.=Veuillez coller ces informations (avec Ctrl+V) dans la description de l'anomalie. +Last\ notification=Dernière notification +Check\ the\ event\ log\ to\ see\ all\ notifications=Consultez le journal des événements pour voir toutes les notifications Host=Hôte Port=Port @@ -2477,4 +2496,13 @@ Success\!\ Finished\ writing\ metadata.=Succès \! Écriture des métadonnées t Custom\ API\ key=Clef d'API personnalisée Check\ %0\ API\ Key\ Setting=Vérifier les paramètres de la clef d'API %0 +Two\ fields=Deux champs +Overwrite\ Non\ empty\ fields=Écraser les champs non vides +Field\ value=Valeur du champ +Rename=Renommer +New\ field\ name=Nouveau nom de champ +Copy\ value=Copier la valeur +Move\ value=Déplacer la valeur +Swap\ values=Permuter les valeurs +Copy\ or\ move\ the\ value\ of\ one\ field\ to\ another=Copier ou déplacer la valeur d'un champ vers un autre From cd41230697708480cee69eebcd605c14f05d2508 Mon Sep 17 00:00:00 2001 From: Houssem Nasri Date: Mon, 25 Jul 2022 13:43:53 +0100 Subject: [PATCH 02/31] Fix external group metadata changes are not merged (#8994) --- CHANGELOG.md | 1 + .../java/org/jabref/gui/collab/MetaDataChangeViewModel.java | 3 +++ .../java/org/jabref/logic/bibtex/comparator/GroupDiff.java | 2 +- .../java/org/jabref/logic/bibtex/comparator/GroupDiffTest.java | 2 +- 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f70513fe9..0cbe3da8da2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed a bug where switching between themes will cause an error/exception. [#8939](https://github.com/JabRef/jabref/pull/8939) - We fixed a bug where files that were deleted in the source bibtex file were kept in the index. [#8962](https://github.com/JabRef/jabref/pull/8962) - We fixed "Error while sending to JabRef" when the browser extension interacts with JabRef. [JabRef-Browser-Extension#479](https://github.com/JabRef/JabRef-Browser-Extension/issues/479) +- We fixed a bug that prevented external group metadata changes from being merged. [#8873](https://github.com/JabRef/jabref/issues/8873) ### Removed diff --git a/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java b/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java index 83b6351fd78..a60bccaa6e9 100644 --- a/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/MetaDataChangeViewModel.java @@ -39,5 +39,8 @@ public Node description() { @Override public void makeChange(BibDatabaseContext database, NamedCompound undoEdit) { database.setMetaData(metaDataDiff.getNewMetaData()); + // group change is handled by GroupChangeViewModel, so we set the groups root to the original value + // to prevent any inconsistency + metaDataDiff.getGroupDifferences().ifPresent(groupDiff -> database.getMetaData().setGroups(groupDiff.getOriginalGroupRoot())); } } diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java index 4914ed9642d..35c2e5a1338 100644 --- a/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java +++ b/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java @@ -23,7 +23,7 @@ public static Optional compare(MetaData originalMetaData, MetaData ne final Optional newGroups = newMetaData.getGroups(); if (!originalGroups.equals(newGroups)) { - return Optional.of(new GroupDiff(newGroups.orElse(null), originalGroups.orElse(null))); + return Optional.of(new GroupDiff(originalGroups.orElse(null), newGroups.orElse(null))); } else { return Optional.empty(); } diff --git a/src/test/java/org/jabref/logic/bibtex/comparator/GroupDiffTest.java b/src/test/java/org/jabref/logic/bibtex/comparator/GroupDiffTest.java index 1f1c20fc329..9fded7c7802 100644 --- a/src/test/java/org/jabref/logic/bibtex/comparator/GroupDiffTest.java +++ b/src/test/java/org/jabref/logic/bibtex/comparator/GroupDiffTest.java @@ -56,7 +56,7 @@ void compareWithChangedGroup() { Optional groupDiff = GroupDiff.compare(originalMetaData, newMetaData); - Optional expectedGroupDiff = Optional.of(new GroupDiff(newMetaData.getGroups().get(), originalMetaData.getGroups().get())); + Optional expectedGroupDiff = Optional.of(new GroupDiff(originalMetaData.getGroups().get(), newMetaData.getGroups().get())); assertEquals(expectedGroupDiff.get().getNewGroupRoot(), groupDiff.get().getNewGroupRoot()); assertEquals(expectedGroupDiff.get().getOriginalGroupRoot(), groupDiff.get().getOriginalGroupRoot()); From c51a4863cfcba90028470fcee96c1c5b1913c368 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:37:03 +0200 Subject: [PATCH 03/31] Bump java-diff-utils from 4.11 to 4.12 (#8998) Bumps [java-diff-utils](https://github.com/java-diff-utils/java-diff-utils) from 4.11 to 4.12. - [Release notes](https://github.com/java-diff-utils/java-diff-utils/releases) - [Changelog](https://github.com/java-diff-utils/java-diff-utils/blob/master/CHANGELOG.md) - [Commits](https://github.com/java-diff-utils/java-diff-utils/compare/java-diff-utils-parent-4.11...java-diff-utils-parent-4.12) --- updated-dependencies: - dependency-name: io.github.java-diff-utils:java-diff-utils dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e088688f87a..6edbc237980 100644 --- a/build.gradle +++ b/build.gradle @@ -136,7 +136,7 @@ dependencies { implementation 'org.libreoffice:libreoffice:7.3.4' implementation 'org.libreoffice:unoloader:7.3.4' - implementation 'io.github.java-diff-utils:java-diff-utils:4.11' + implementation 'io.github.java-diff-utils:java-diff-utils:4.12' implementation 'info.debatty:java-string-similarity:2.0.0' antlr3 'org.antlr:antlr:3.5.3' From 7df24b9b721ffd11455fec46986dfe863e374ee5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:37:58 +0200 Subject: [PATCH 04/31] Bump libreoffice from 7.3.4 to 7.3.5 (#8997) Bumps libreoffice from 7.3.4 to 7.3.5. --- updated-dependencies: - dependency-name: org.libreoffice:libreoffice dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6edbc237980..1696cbfd729 100644 --- a/build.gradle +++ b/build.gradle @@ -133,7 +133,7 @@ dependencies { implementation 'commons-cli:commons-cli:1.5.0' - implementation 'org.libreoffice:libreoffice:7.3.4' + implementation 'org.libreoffice:libreoffice:7.3.5' implementation 'org.libreoffice:unoloader:7.3.4' implementation 'io.github.java-diff-utils:java-diff-utils:4.12' From 160b805921d0d7c13f94ae20f375082ecdc0dd4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:04:59 +0200 Subject: [PATCH 05/31] Bump unoloader from 7.3.4 to 7.3.5 (#8996) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1696cbfd729..3469f67e6ea 100644 --- a/build.gradle +++ b/build.gradle @@ -134,7 +134,7 @@ dependencies { implementation 'commons-cli:commons-cli:1.5.0' implementation 'org.libreoffice:libreoffice:7.3.5' - implementation 'org.libreoffice:unoloader:7.3.4' + implementation 'org.libreoffice:unoloader:7.3.5' implementation 'io.github.java-diff-utils:java-diff-utils:4.12' implementation 'info.debatty:java-string-similarity:2.0.0' From 8ab76350cfe2477b412e21b60dda8b6e63386175 Mon Sep 17 00:00:00 2001 From: Houssem Nasri Date: Tue, 26 Jul 2022 09:09:24 +0100 Subject: [PATCH 06/31] Sync group view mode and main table (#8993) --- CHANGELOG.md | 1 + .../java/org/jabref/gui/maintable/MainTableDataModel.java | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cbe3da8da2..b39122eeeb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed a bug where switching between themes will cause an error/exception. [#8939](https://github.com/JabRef/jabref/pull/8939) - We fixed a bug where files that were deleted in the source bibtex file were kept in the index. [#8962](https://github.com/JabRef/jabref/pull/8962) - We fixed "Error while sending to JabRef" when the browser extension interacts with JabRef. [JabRef-Browser-Extension#479](https://github.com/JabRef/JabRef-Browser-Extension/issues/479) +- We fixed a bug where updating group view mode (intersection or union) requires re-selecting groups to take effect. [#6998](https://github.com/JabRef/jabref/issues/6998) - We fixed a bug that prevented external group metadata changes from being merged. [#8873](https://github.com/JabRef/jabref/issues/8873) ### Removed diff --git a/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java b/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java index 5210613e4de..6ef5e500218 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java @@ -47,7 +47,10 @@ public MainTableDataModel(BibDatabaseContext context, PreferencesService prefere entriesFiltered = new FilteredList<>(entriesViewModel); entriesFiltered.predicateProperty().bind( - EasyBind.combine(stateManager.activeGroupProperty(), stateManager.activeSearchQueryProperty(), (groups, query) -> entry -> isMatched(groups, query, entry)) + EasyBind.combine(stateManager.activeGroupProperty(), + stateManager.activeSearchQueryProperty(), + groupsPreferences.groupViewModeProperty(), + (groups, query, groupViewMode) -> entry -> isMatched(groups, query, entry)) ); IntegerProperty resultSize = new SimpleIntegerProperty(); From 36c64dce792540e4aa431a989e8e94333e8235c7 Mon Sep 17 00:00:00 2001 From: Christoph Date: Tue, 26 Jul 2022 17:42:14 +0200 Subject: [PATCH 07/31] New Crowdin updates (#8999) --- src/main/resources/l10n/JabRef_fr.properties | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index f65aa14b90a..046139b916d 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -839,7 +839,7 @@ Import\ error=Erreur d'importation Open\ library\ error=Erreur d'ouverture du fichier Please\ check\ your\ library\ file\ for\ wrong\ syntax.=Veuillez vérifier la syntaxe de votre fichier bibliographique. SourceTab\ error=Erreur de SourceTab -User\ input\ via\ entry-editor\ in\ `{}bibtex\ source`\ tab\ led\ to\ failure.=L'entrée de l'utilisateur via l'éditeur d'entrée dans l'onglet `{}bibtex source` a conduit à un échec. +User\ input\ via\ entry-editor\ in\ `{}bibtex\ source`\ tab\ led\ to\ failure.=La saisie de l'utilisateur via l'éditeur d'entrée dans l'onglet `{}bibtex source` a conduit à un échec. Sort\ subgroups=Trier les sous-groupes @@ -2496,13 +2496,22 @@ Success\!\ Finished\ writing\ metadata.=Succès \! Écriture des métadonnées t Custom\ API\ key=Clef d'API personnalisée Check\ %0\ API\ Key\ Setting=Vérifier les paramètres de la clef d'API %0 +Edit\ field\ value=Modifier la valeur du champ Two\ fields=Deux champs Overwrite\ Non\ empty\ fields=Écraser les champs non vides +Set=Définir +Append=Ajouter +Clear\ field=Vider le champ Field\ value=Valeur du champ +Edit\ field\ value\ of\ selected\ entries=Modifier la valeur du champ pour les entrées sélectionnées Rename=Renommer New\ field\ name=Nouveau nom de champ Copy\ value=Copier la valeur Move\ value=Déplacer la valeur Swap\ values=Permuter les valeurs Copy\ or\ move\ the\ value\ of\ one\ field\ to\ another=Copier ou déplacer la valeur d'un champ vers un autre +Automatic\ field\ editor=Éditeur automatique de champs +(Note\:\ If\ original\ entries\ lack\ keywords\ to\ qualify\ for\ the\ new\ group\ configuration,\ confirming\ here\ will\ add\ them)=(Note \: si les entrées originales n'ont pas de mots-clefs correspondant à la nouvelle configuration du groupe, confirmer ici les ajoutera) +Assign=Assigner +Do\ not\ assign=Ne pas assigner From bd8ede5cd828cdb4e6ac867e52daffdd7c4af61f Mon Sep 17 00:00:00 2001 From: Michael Mauersberger <67641254+m-mauersberger@users.noreply.github.com> Date: Fri, 29 Jul 2022 13:57:15 +0200 Subject: [PATCH 08/31] Autosave folder and checkbox is remembered (#9000) --- CHANGELOG.md | 1 + .../SharedDatabaseLoginDialogViewModel.java | 8 ++++++++ .../prefs/SharedDatabasePreferences.java | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b39122eeeb1..ebf3434f2bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed "Error while sending to JabRef" when the browser extension interacts with JabRef. [JabRef-Browser-Extension#479](https://github.com/JabRef/JabRef-Browser-Extension/issues/479) - We fixed a bug where updating group view mode (intersection or union) requires re-selecting groups to take effect. [#6998](https://github.com/JabRef/jabref/issues/6998) - We fixed a bug that prevented external group metadata changes from being merged. [#8873](https://github.com/JabRef/jabref/issues/8873) +- We fixed the shared database opening dialog to remember autosave folder and tick. [#7516](https://github.com/JabRef/jabref/issues/7516) ### Removed diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java index 43bf6f86dac..970d36db98e 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseLoginDialogViewModel.java @@ -212,6 +212,9 @@ private void setPreferences() { } sharedDatabasePreferences.setRememberPassword(rememberPassword.get()); + + sharedDatabasePreferences.setFolder(folder.getValue()); + sharedDatabasePreferences.setAutosave(autosave.get()); } /** @@ -225,6 +228,8 @@ private void applyPreferences() { Optional sharedDatabaseUser = sharedDatabasePreferences.getUser(); Optional sharedDatabasePassword = sharedDatabasePreferences.getPassword(); boolean sharedDatabaseRememberPassword = sharedDatabasePreferences.getRememberPassword(); + Optional sharedDatabaseFolder = sharedDatabasePreferences.getFolder(); + boolean sharedDatabaseAutosave = sharedDatabasePreferences.getAutosave(); Optional sharedDatabaseKeystoreFile = sharedDatabasePreferences.getKeyStoreFile(); if (sharedDatabaseType.isPresent()) { @@ -248,6 +253,9 @@ private void applyPreferences() { } rememberPassword.set(sharedDatabaseRememberPassword); + + sharedDatabaseFolder.ifPresent(folder::set); + autosave.set(sharedDatabaseAutosave); } private boolean isSharedDatabaseAlreadyPresent(DBMSConnectionProperties connectionProperties) { diff --git a/src/main/java/org/jabref/logic/shared/prefs/SharedDatabasePreferences.java b/src/main/java/org/jabref/logic/shared/prefs/SharedDatabasePreferences.java index 260ec297c24..4b1a14fe1c9 100644 --- a/src/main/java/org/jabref/logic/shared/prefs/SharedDatabasePreferences.java +++ b/src/main/java/org/jabref/logic/shared/prefs/SharedDatabasePreferences.java @@ -25,6 +25,8 @@ public class SharedDatabasePreferences { private static final String SHARED_DATABASE_NAME = "sharedDatabaseName"; private static final String SHARED_DATABASE_USER = "sharedDatabaseUser"; private static final String SHARED_DATABASE_PASSWORD = "sharedDatabasePassword"; + private static final String SHARED_DATABASE_FOLDER = "sharedDatabaseFolder"; + private static final String SHARED_DATABASE_AUTOSAVE = "sharedDatabaseAutosave"; private static final String SHARED_DATABASE_REMEMBER_PASSWORD = "sharedDatabaseRememberPassword"; private static final String SHARED_DATABASE_USE_SSL = "sharedDatabaseUseSSL"; private static final String SHARED_DATABASE_KEYSTORE_FILE = "sharedDatabaseKeyStoreFile"; @@ -77,6 +79,14 @@ public boolean getRememberPassword() { return internalPrefs.getBoolean(SHARED_DATABASE_REMEMBER_PASSWORD, false); } + public Optional getFolder() { + return getOptionalValue(SHARED_DATABASE_FOLDER); + } + + public boolean getAutosave() { + return internalPrefs.getBoolean(SHARED_DATABASE_AUTOSAVE, false); + } + public boolean isUseSSL() { return internalPrefs.getBoolean(SHARED_DATABASE_USE_SSL, false); } @@ -109,6 +119,14 @@ public void setRememberPassword(boolean rememberPassword) { internalPrefs.putBoolean(SHARED_DATABASE_REMEMBER_PASSWORD, rememberPassword); } + public void setFolder(String folder) { + internalPrefs.put(SHARED_DATABASE_FOLDER, folder); + } + + public void setAutosave(boolean autosave) { + internalPrefs.putBoolean(SHARED_DATABASE_AUTOSAVE, autosave); + } + public void setUseSSL(boolean useSSL) { internalPrefs.putBoolean(SHARED_DATABASE_USE_SSL, useSSL); } From 871c8d749be52a96fef3900a52084359b31fcaf9 Mon Sep 17 00:00:00 2001 From: github actions Date: Mon, 1 Aug 2022 02:59:04 +0000 Subject: [PATCH 09/31] Squashed 'buildres/csl/csl-styles/' changes from 3d3573c8db..c750b6e69a MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit c750b6e69a APA: Put conditional event-title logic in a macro (#6161) a87414fa90 Remove month from association-for-compuational-linguistics.csl (#6158) 6153db0eac Remove issue numbers from BJOC style (#6155) e231ea3345 Bug fix for `event` regression (#6154) 0dab65132a Add event-title to other APA styles (#6153) 698cf1cf66 APA: `event-title` and conditional `event` (#6152) 58d3f8fb3a Update vancouver-author-date.csl (#6148) f1638a95c8 add substitute to Vancouver author date (#6147) 39fede595c Update associacao-brasileira-de-normas-tecnicas.csl (#6138) fde7695eae Include chapter title (#6140) 1e3d8b4cf5 Update n.d. abbreivation for DGP style (#6136) ebb728bd1d suffix '.' after first group; changed e-mail (#6135) eed4f07ec0 Update and rename sciences-po-ecole-doctorale-note-french.csl to scie… (#6127) f194647528 Delete TU Dresden Medizin as requested by library (#6131) d8423d84cd Create entomological-review.csl (#6120) 064a3946ba Create australasian-journal-of-philosophy.csl (#6063) a998ded21e Add composer.json (#5668) 37083c9da0 Update copernicus-publications.csl (#6062) 694c97b528 Create chaucer review (#6061) 625a42450c Create haffner-style-manual.csl (#6054) 8b7224b37b make annals-of-allergy-asthma-and-immunology independent (#6041) 710748c501 Create university-of-pretoria-harvard-theology-religion.csl (#6106) d16dffd6d7 Create health-physics.csl (#6040) ca9e184819 Update style-manual-australian-government.csl (#6119) e4122774a1 Create chemical-engineering-technology.csl (#6039) bebdb48687 Create bibliothek-forschung-und-praxis.csl (#6038) 29e49cd196 Update nature.csl (#6117) 891897d755 fix short title for SBL (#6118) git-subtree-dir: buildres/csl/csl-styles git-subtree-split: c750b6e69ae229823d34f57794744d129677bcee --- .github/workflows/merge.yaml | 39 +- annals-of-allergy-asthma-and-immunology.csl | 267 ++++++ apa-5th-edition.csl | 22 +- apa-6th-edition-no-ampersand.csl | 60 +- apa-6th-edition.csl | 60 +- apa-annotated-bibliography.csl | 30 +- apa-cv.csl | 28 +- apa-fr-provost.csl | 22 +- apa-no-ampersand.csl | 32 +- apa-no-doi-no-issue.csl | 60 +- apa-no-initials.csl | 30 +- apa-numeric-superscript-brackets.csl | 30 +- apa-numeric-superscript.csl | 30 +- apa-old-doi-prefix.csl | 60 +- apa-single-spaced.csl | 32 +- apa-tr.csl | 22 +- apa-with-abstract.csl | 30 +- apa.csl | 30 +- associacao-brasileira-de-normas-tecnicas.csl | 4 +- association-for-computational-linguistics.csl | 22 +- australasian-journal-of-philosophy.csl | 317 +++++++ beilstein-journal-of-organic-chemistry.csl | 3 +- bibliothek-forschung-und-praxis.csl | 623 ++++++++++++++ chaucer-review.csl | 775 ++++++++++++++++++ chemical-engineering-technology.csl | 173 ++++ composer.json | 13 + copernicus-publications.csl | 2 +- ...nnals-of-allergy-asthma-and-immunology.csl | 14 - deutsche-gesellschaft-fur-psychologie.csl | 1 + entomological-review.csl | 231 ++++++ haffner-style-manual.csl | 512 ++++++++++++ health-physics.csl | 161 ++++ ...esponsibility-in-education-and-science.csl | 8 +- nature-no-et-al.csl | 26 +- nature-no-superscript.csl | 26 +- nature.csl | 26 +- renamed-styles.json | 3 +- science.csl | 1 + sciences-po-ecole-doctorale-author-date.csl | 4 +- sciences-po-ecole-doctorale-note-french.csl | 10 +- ...lical-literature-fullnote-bibliography.csl | 2 +- spec/filters.yaml | 1 + style-manual-australian-government.csl | 10 +- technische-universitat-dresden-medizin.csl | 265 ------ ...-of-pretoria-harvard-theology-religion.csl | 347 ++++++++ vancouver-author-date.csl | 33 +- 46 files changed, 3991 insertions(+), 506 deletions(-) create mode 100644 annals-of-allergy-asthma-and-immunology.csl create mode 100644 australasian-journal-of-philosophy.csl create mode 100644 bibliothek-forschung-und-praxis.csl create mode 100644 chaucer-review.csl create mode 100644 chemical-engineering-technology.csl create mode 100644 composer.json delete mode 100644 dependent/annals-of-allergy-asthma-and-immunology.csl create mode 100644 entomological-review.csl create mode 100644 haffner-style-manual.csl create mode 100644 health-physics.csl delete mode 100644 technische-universitat-dresden-medizin.csl create mode 100644 university-of-pretoria-harvard-theology-religion.csl diff --git a/.github/workflows/merge.yaml b/.github/workflows/merge.yaml index 0a0d9db955e..7e5736cc960 100644 --- a/.github/workflows/merge.yaml +++ b/.github/workflows/merge.yaml @@ -48,6 +48,10 @@ jobs: - deleted: [ '*.csl', 'dependent/*.csl', '*.xml' ] workflows: - added|modified: .github/workflows/*.yaml + updated_composer: + - added|modified: composer.json + deleted_composer: + - deleted: composer.json - name: Changed files if: github.event_name == 'push' @@ -87,11 +91,24 @@ jobs: run: cd release && git rm ${{ steps.update.outputs.deleted_files }} if: github.event_name == 'push' && steps.update.outputs.deleted == 'true' + - name: Update composer.json + if: steps.update.outputs.updated_composer == 'true' + run: | + cp composer.json release/composer.json + cd release + git add composer.json + + - name: Delete composer.json + if: steps.update.outputs.deleted_composer == 'true' + run: | + cd release + git rm composer.json + - uses: stefanzweifel/git-auto-commit-action@v4 with: repository: 'release' - commit_message: Releasing ${{ steps.update.outputs.updated_files }} ${{ steps.update.outputs.deleted_files }} - if: github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true') + commit_message: Releasing ${{ steps.update.outputs.updated_files }} ${{ steps.update.outputs.deleted_files }} ${{ steps.update.outputs.updated_composer_files }} ${{ steps.update.outputs.deleted_composer_files }} + if: github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true' || steps.update.outputs.updated_composer == 'true' || steps.update.outputs.deleted_composer == 'true') - uses: stefanzweifel/git-auto-commit-action@v4 with: @@ -116,3 +133,21 @@ jobs: git-user: "csl-bot" git-user-email: github@citationstyles.org git-commit-message: copied ${{ steps.update.outputs.workflows_files }} from styles + + - name: Bump version and push tag + id: tag_version + uses: mathieudutour/github-tag-action@v5.6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + default_bump: patch + if: github.event_name == 'push' && hashFiles('composer.json') != '' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true') + + - name: Create a GitHub release + uses: softprops/action-gh-release@v0.1.14 + env: + github_token: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.tag_version.outputs.new_tag }} + name: Release ${{ steps.tag_version.outputs.new_tag }} + body: Released ${{ steps.update.outputs.updated_files }} ${{ steps.update.outputs.deleted_files }} ${{ steps.update.outputs.updated_composer_files }} ${{ steps.update.outputs.deleted_composer_files }} + if: github.event_name == 'push' && hashFiles('composer.json') != '' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true') diff --git a/annals-of-allergy-asthma-and-immunology.csl b/annals-of-allergy-asthma-and-immunology.csl new file mode 100644 index 00000000000..b612c5ff55f --- /dev/null +++ b/annals-of-allergy-asthma-and-immunology.csl @@ -0,0 +1,267 @@ + + diff --git a/apa-5th-edition.csl b/apa-5th-edition.csl index a2784ca4b5b..87cc83196a3 100644 --- a/apa-5th-edition.csl +++ b/apa-5th-edition.csl @@ -183,7 +183,7 @@ - + @@ -196,23 +196,37 @@ - + - + - + + + + + + + + + + + + diff --git a/apa-6th-edition-no-ampersand.csl b/apa-6th-edition-no-ampersand.csl index b0f618ec31f..ff55b46b98d 100644 --- a/apa-6th-edition-no-ampersand.csl +++ b/apa-6th-edition-no-ampersand.csl @@ -897,32 +897,52 @@ - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + @@ -1401,7 +1421,7 @@ - diff --git a/apa-6th-edition.csl b/apa-6th-edition.csl index 775857accbe..851a3096ab0 100644 --- a/apa-6th-edition.csl +++ b/apa-6th-edition.csl @@ -896,32 +896,52 @@ - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + @@ -1400,7 +1420,7 @@ - diff --git a/apa-annotated-bibliography.csl b/apa-annotated-bibliography.csl index 014ae44f162..0636d93db58 100644 --- a/apa-annotated-bibliography.csl +++ b/apa-annotated-bibliography.csl @@ -320,8 +320,8 @@ @@ -389,7 +389,7 @@ - @@ -470,7 +470,7 @@ - @@ -529,7 +529,7 @@ - @@ -1294,7 +1294,7 @@ - @@ -1559,7 +1559,7 @@ - + @@ -1567,6 +1567,20 @@ + + + + + + + + + + + @@ -1574,7 +1588,7 @@ - diff --git a/apa-cv.csl b/apa-cv.csl index 5a766c3dc61..228ad3ee882 100644 --- a/apa-cv.csl +++ b/apa-cv.csl @@ -320,8 +320,8 @@ @@ -415,7 +415,7 @@ - @@ -1085,7 +1085,7 @@ - @@ -1301,12 +1301,12 @@ + both publisher-place and event-place. Remove this 'choose' when that is changed. --> - + @@ -1314,6 +1314,20 @@ + + + + + + + + + + + @@ -1321,7 +1335,7 @@ - diff --git a/apa-fr-provost.csl b/apa-fr-provost.csl index 4b068cf752d..9e7928dca71 100644 --- a/apa-fr-provost.csl +++ b/apa-fr-provost.csl @@ -245,7 +245,7 @@ - + @@ -298,17 +298,17 @@ - + - + - + @@ -317,6 +317,20 @@ + + + + + + + + + + + diff --git a/apa-no-ampersand.csl b/apa-no-ampersand.csl index 446103abb69..5759e34ec4a 100644 --- a/apa-no-ampersand.csl +++ b/apa-no-ampersand.csl @@ -320,8 +320,8 @@ @@ -389,7 +389,7 @@ - @@ -470,7 +470,7 @@ - @@ -529,7 +529,7 @@ - @@ -1294,7 +1294,7 @@ - @@ -1554,12 +1554,12 @@ + both publisher-place and event-place. Remove this 'choose' when that is changed. --> - + @@ -1567,6 +1567,20 @@ + + + + + + + + + + + @@ -1574,7 +1588,7 @@ - diff --git a/apa-no-doi-no-issue.csl b/apa-no-doi-no-issue.csl index 08306362d76..c72c8bdf126 100644 --- a/apa-no-doi-no-issue.csl +++ b/apa-no-doi-no-issue.csl @@ -903,32 +903,52 @@ - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + @@ -1414,7 +1434,7 @@ - diff --git a/apa-no-initials.csl b/apa-no-initials.csl index b8bcab11e79..4695cd9b6fb 100644 --- a/apa-no-initials.csl +++ b/apa-no-initials.csl @@ -320,8 +320,8 @@ @@ -389,7 +389,7 @@ - @@ -470,7 +470,7 @@ - @@ -529,7 +529,7 @@ - @@ -1294,7 +1294,7 @@ - @@ -1559,7 +1559,7 @@ - + @@ -1567,6 +1567,20 @@ + + + + + + + + + + + @@ -1574,7 +1588,7 @@ - diff --git a/apa-numeric-superscript-brackets.csl b/apa-numeric-superscript-brackets.csl index c6d3de36f82..ba3dadf6d20 100644 --- a/apa-numeric-superscript-brackets.csl +++ b/apa-numeric-superscript-brackets.csl @@ -123,8 +123,8 @@ @@ -192,7 +192,7 @@ - @@ -273,7 +273,7 @@ - @@ -332,7 +332,7 @@ - @@ -1097,7 +1097,7 @@ - @@ -1362,7 +1362,7 @@ - + @@ -1370,6 +1370,20 @@ + + + + + + + + + + + @@ -1377,7 +1391,7 @@ - diff --git a/apa-numeric-superscript.csl b/apa-numeric-superscript.csl index 0a6af38540b..1ed0ae88b7c 100644 --- a/apa-numeric-superscript.csl +++ b/apa-numeric-superscript.csl @@ -123,8 +123,8 @@ @@ -192,7 +192,7 @@ - @@ -273,7 +273,7 @@ - @@ -332,7 +332,7 @@ - @@ -1097,7 +1097,7 @@ - @@ -1362,7 +1362,7 @@ - + @@ -1370,6 +1370,20 @@ + + + + + + + + + + + @@ -1377,7 +1391,7 @@ - diff --git a/apa-old-doi-prefix.csl b/apa-old-doi-prefix.csl index 02ecfc75fac..cdfaa73a451 100644 --- a/apa-old-doi-prefix.csl +++ b/apa-old-doi-prefix.csl @@ -897,32 +897,52 @@ - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + @@ -1401,7 +1421,7 @@ - diff --git a/apa-single-spaced.csl b/apa-single-spaced.csl index c2157cb296a..ff224f6a405 100644 --- a/apa-single-spaced.csl +++ b/apa-single-spaced.csl @@ -320,8 +320,8 @@ @@ -389,7 +389,7 @@ - @@ -470,7 +470,7 @@ - @@ -529,7 +529,7 @@ - @@ -1294,7 +1294,7 @@ - @@ -1554,12 +1554,12 @@ + both publisher-place and event-place. Remove this 'choose' when that is changed. --> - + @@ -1567,6 +1567,20 @@ + + + + + + + + + + + @@ -1574,7 +1588,7 @@ - diff --git a/apa-tr.csl b/apa-tr.csl index 557066dafd8..d17be770985 100644 --- a/apa-tr.csl +++ b/apa-tr.csl @@ -223,7 +223,7 @@ - + @@ -241,15 +241,15 @@ - + - + - + @@ -257,6 +257,20 @@ + + + + + + + + + + + diff --git a/apa-with-abstract.csl b/apa-with-abstract.csl index ddff715c9d6..25d185866bf 100644 --- a/apa-with-abstract.csl +++ b/apa-with-abstract.csl @@ -320,8 +320,8 @@ @@ -389,7 +389,7 @@ - @@ -470,7 +470,7 @@ - @@ -529,7 +529,7 @@ - @@ -1294,7 +1294,7 @@ - @@ -1559,7 +1559,7 @@ - + @@ -1567,6 +1567,20 @@ + + + + + + + + + + + @@ -1574,7 +1588,7 @@ - diff --git a/apa.csl b/apa.csl index 09f4635a505..0fd6bd71a5e 100644 --- a/apa.csl +++ b/apa.csl @@ -320,8 +320,8 @@ @@ -389,7 +389,7 @@ - @@ -470,7 +470,7 @@ - @@ -530,7 +530,7 @@ - @@ -1295,7 +1295,7 @@ - @@ -1560,7 +1560,7 @@ - + @@ -1568,6 +1568,20 @@ + + + + + + + + + + + @@ -1575,7 +1589,7 @@ - diff --git a/associacao-brasileira-de-normas-tecnicas.csl b/associacao-brasileira-de-normas-tecnicas.csl index 2b769703f27..cc8975fa8c2 100644 --- a/associacao-brasileira-de-normas-tecnicas.csl +++ b/associacao-brasileira-de-normas-tecnicas.csl @@ -82,7 +82,7 @@ tendo as inicias separadas por ponto.--> - - - - - - @@ -199,7 +194,6 @@ - @@ -212,7 +206,6 @@ - @@ -236,7 +229,6 @@ - @@ -244,7 +236,6 @@ - @@ -262,18 +253,10 @@ - - - - - - - - - + @@ -282,7 +265,6 @@ - @@ -293,13 +275,11 @@ - - diff --git a/australasian-journal-of-philosophy.csl b/australasian-journal-of-philosophy.csl new file mode 100644 index 00000000000..ed846706972 --- /dev/null +++ b/australasian-journal-of-philosophy.csl @@ -0,0 +1,317 @@ + + diff --git a/beilstein-journal-of-organic-chemistry.csl b/beilstein-journal-of-organic-chemistry.csl index 8487b04c1ee..2ab0fe83caf 100644 --- a/beilstein-journal-of-organic-chemistry.csl +++ b/beilstein-journal-of-organic-chemistry.csl @@ -3,7 +3,7 @@ Beilstein Journal of Organic Chemistry - Beilstein + BJOC http://www.zotero.org/styles/beilstein-journal-of-organic-chemistry @@ -154,7 +154,6 @@ - diff --git a/bibliothek-forschung-und-praxis.csl b/bibliothek-forschung-und-praxis.csl new file mode 100644 index 00000000000..5e85189c238 --- /dev/null +++ b/bibliothek-forschung-und-praxis.csl @@ -0,0 +1,623 @@ + + diff --git a/chaucer-review.csl b/chaucer-review.csl new file mode 100644 index 00000000000..123910b954a --- /dev/null +++ b/chaucer-review.csl @@ -0,0 +1,775 @@ + + diff --git a/chemical-engineering-technology.csl b/chemical-engineering-technology.csl new file mode 100644 index 00000000000..8497a7049e3 --- /dev/null +++ b/chemical-engineering-technology.csl @@ -0,0 +1,173 @@ + + diff --git a/composer.json b/composer.json new file mode 100644 index 00000000000..7906f93ba75 --- /dev/null +++ b/composer.json @@ -0,0 +1,13 @@ +{ + "name": "citation-style-language/styles", + "description": "Citation Style Language (CSL) Styless", + "type": "library", + "license": "CC-BY-SA-3.0", + "homepage": "http://citationstyles.org/", + "authors": [ + { + "name": "Citation Style Language (CSL) Team", + "homepage": "http://citationstyles.org/about/#Credits" + } + ] +} diff --git a/copernicus-publications.csl b/copernicus-publications.csl index 1745d371a3f..018ec7cb15c 100644 --- a/copernicus-publications.csl +++ b/copernicus-publications.csl @@ -255,7 +255,7 @@ - + diff --git a/dependent/annals-of-allergy-asthma-and-immunology.csl b/dependent/annals-of-allergy-asthma-and-immunology.csl deleted file mode 100644 index 12086db4e67..00000000000 --- a/dependent/annals-of-allergy-asthma-and-immunology.csl +++ /dev/null @@ -1,14 +0,0 @@ - - diff --git a/deutsche-gesellschaft-fur-psychologie.csl b/deutsche-gesellschaft-fur-psychologie.csl index e6795a3b3e4..2f7e4e8243e 100644 --- a/deutsche-gesellschaft-fur-psychologie.csl +++ b/deutsche-gesellschaft-fur-psychologie.csl @@ -33,6 +33,7 @@ et al. Zugriff am Original erschienen + n. d. diff --git a/entomological-review.csl b/entomological-review.csl new file mode 100644 index 00000000000..7b078499259 --- /dev/null +++ b/entomological-review.csl @@ -0,0 +1,231 @@ + + diff --git a/haffner-style-manual.csl b/haffner-style-manual.csl new file mode 100644 index 00000000000..279bbd8156a --- /dev/null +++ b/haffner-style-manual.csl @@ -0,0 +1,512 @@ + + diff --git a/health-physics.csl b/health-physics.csl new file mode 100644 index 00000000000..dc69ba1cefe --- /dev/null +++ b/health-physics.csl @@ -0,0 +1,161 @@ + + diff --git a/journal-on-efficiency-and-responsibility-in-education-and-science.csl b/journal-on-efficiency-and-responsibility-in-education-and-science.csl index 43ac15cd67d..e3387113c32 100644 --- a/journal-on-efficiency-and-responsibility-in-education-and-science.csl +++ b/journal-on-efficiency-and-responsibility-in-education-and-science.csl @@ -10,7 +10,7 @@ Dominik Bláha - blahad@sic.czu.cz + blahad@lib.czu.cz Igor Krejčí @@ -20,7 +20,7 @@ 2336-2375 1803-1617 - 2019-04-20T15:03:39+00:00 + 2022-07-10T17:58:51+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -618,8 +618,8 @@ - - + + diff --git a/nature-no-et-al.csl b/nature-no-et-al.csl index 60dbfb6c761..115b680cb44 100644 --- a/nature-no-et-al.csl +++ b/nature-no-et-al.csl @@ -12,7 +12,7 @@ - 2014-06-22T12:00:00+00:00 + 2014-06-23T03:22:15+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -35,7 +35,7 @@ - + @@ -55,6 +55,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/nature-no-superscript.csl b/nature-no-superscript.csl index 225eaf50c59..6c04d8139b3 100644 --- a/nature-no-superscript.csl +++ b/nature-no-superscript.csl @@ -12,7 +12,7 @@ - 2014-06-03T22:14:43+00:00 + 2022-07-02T11:44:17+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -35,7 +35,7 @@ - + @@ -55,6 +55,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/nature.csl b/nature.csl index 2646cfe922c..a2253b8b817 100644 --- a/nature.csl +++ b/nature.csl @@ -15,7 +15,7 @@ 0028-0836 1476-4687 - 2019-10-08T13:18:12+00:00 + 2022-07-01T16:03:34+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -37,7 +37,7 @@ - + @@ -55,6 +55,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/renamed-styles.json b/renamed-styles.json index d5396d84489..e30246291be 100644 --- a/renamed-styles.json +++ b/renamed-styles.json @@ -529,5 +529,6 @@ "investigaciones-de-historia-economica-economic-history-research": "investigaciones-de-historia-economica", "reach-reviews-in-human-space-exploration": "reach", "journal-of-oncological-science": "journal-of-oncological-sciences", - "harvard-dublin-city-university": "harvard-cite-them-right" + "harvard-dublin-city-university": "harvard-cite-them-right", + "technische-universitat-dresden-medizin": "apa" } diff --git a/science.csl b/science.csl index 641de8f592d..06ee7b70359 100644 --- a/science.csl +++ b/science.csl @@ -144,6 +144,7 @@ + diff --git a/sciences-po-ecole-doctorale-author-date.csl b/sciences-po-ecole-doctorale-author-date.csl index 8b570113983..3fe5189cd70 100644 --- a/sciences-po-ecole-doctorale-author-date.csl +++ b/sciences-po-ecole-doctorale-author-date.csl @@ -1,7 +1,7 @@ - diff --git a/university-of-pretoria-harvard-theology-religion.csl b/university-of-pretoria-harvard-theology-religion.csl new file mode 100644 index 00000000000..f59a9edfa0a --- /dev/null +++ b/university-of-pretoria-harvard-theology-religion.csl @@ -0,0 +1,347 @@ + + diff --git a/vancouver-author-date.csl b/vancouver-author-date.csl index 13b1bc2d023..4bd51dd6b9d 100644 --- a/vancouver-author-date.csl +++ b/vancouver-author-date.csl @@ -7,6 +7,7 @@ + Charles Parnot charles.parnot@gmail.com @@ -15,7 +16,7 @@ Vancouver style as outlined by International Committee of Medical Journal Editors Uniform Requirements for Manuscripts Submitted to Biomedical Journals: Sample References - 2012-11-09T12:00:00+00:00 + 2022-07-18T16:49:43+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -47,6 +48,16 @@ + + + + + + @@ -253,25 +264,29 @@ - - - - - + + + + - - - + + + + + + From 4ee5553507c43af9829bb979472c8362702b1105 Mon Sep 17 00:00:00 2001 From: github actions Date: Mon, 1 Aug 2022 02:59:10 +0000 Subject: [PATCH 10/31] Squashed 'buildres/csl/csl-locales/' changes from 55459cd79f..e637746677 e637746677 copied .github/workflows/merge.yaml from styles git-subtree-dir: buildres/csl/csl-locales git-subtree-split: e637746677fd1f600173e07b561f0388284ae39b --- .github/workflows/merge.yaml | 39 ++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/.github/workflows/merge.yaml b/.github/workflows/merge.yaml index 0a0d9db955e..7e5736cc960 100644 --- a/.github/workflows/merge.yaml +++ b/.github/workflows/merge.yaml @@ -48,6 +48,10 @@ jobs: - deleted: [ '*.csl', 'dependent/*.csl', '*.xml' ] workflows: - added|modified: .github/workflows/*.yaml + updated_composer: + - added|modified: composer.json + deleted_composer: + - deleted: composer.json - name: Changed files if: github.event_name == 'push' @@ -87,11 +91,24 @@ jobs: run: cd release && git rm ${{ steps.update.outputs.deleted_files }} if: github.event_name == 'push' && steps.update.outputs.deleted == 'true' + - name: Update composer.json + if: steps.update.outputs.updated_composer == 'true' + run: | + cp composer.json release/composer.json + cd release + git add composer.json + + - name: Delete composer.json + if: steps.update.outputs.deleted_composer == 'true' + run: | + cd release + git rm composer.json + - uses: stefanzweifel/git-auto-commit-action@v4 with: repository: 'release' - commit_message: Releasing ${{ steps.update.outputs.updated_files }} ${{ steps.update.outputs.deleted_files }} - if: github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true') + commit_message: Releasing ${{ steps.update.outputs.updated_files }} ${{ steps.update.outputs.deleted_files }} ${{ steps.update.outputs.updated_composer_files }} ${{ steps.update.outputs.deleted_composer_files }} + if: github.event_name == 'push' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true' || steps.update.outputs.updated_composer == 'true' || steps.update.outputs.deleted_composer == 'true') - uses: stefanzweifel/git-auto-commit-action@v4 with: @@ -116,3 +133,21 @@ jobs: git-user: "csl-bot" git-user-email: github@citationstyles.org git-commit-message: copied ${{ steps.update.outputs.workflows_files }} from styles + + - name: Bump version and push tag + id: tag_version + uses: mathieudutour/github-tag-action@v5.6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + default_bump: patch + if: github.event_name == 'push' && hashFiles('composer.json') != '' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true') + + - name: Create a GitHub release + uses: softprops/action-gh-release@v0.1.14 + env: + github_token: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.tag_version.outputs.new_tag }} + name: Release ${{ steps.tag_version.outputs.new_tag }} + body: Released ${{ steps.update.outputs.updated_files }} ${{ steps.update.outputs.deleted_files }} ${{ steps.update.outputs.updated_composer_files }} ${{ steps.update.outputs.deleted_composer_files }} + if: github.event_name == 'push' && hashFiles('composer.json') != '' && (steps.update.outputs.updated == 'true' || steps.update.outputs.deleted == 'true') From 277df6f89e725d69e8076a65ba1ab6b55e1fd3a5 Mon Sep 17 00:00:00 2001 From: Christoph Date: Mon, 1 Aug 2022 09:20:55 +0200 Subject: [PATCH 11/31] New translations JabRef_en.properties (Spanish) (#9003) --- src/main/resources/l10n/JabRef_es.properties | 74 +++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index 5667fb916b0..60e35d98ad0 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -1,7 +1,12 @@ +Could\ not\ delete\ empty\ entries.=No se pudieron eliminar entradas vacías. +Delete\ empty\ entries=Eliminar entradas vacías +Empty\ entries=Vaciar entradas +Keep\ empty\ entries=Mantener entradas vacías +Library\ '%0'\ has\ empty\ entries.\ Do\ you\ want\ to\ delete\ them?=La biblioteca '%0' tiene entradas vacías. ¿Quieres eliminarlas? Unable\ to\ monitor\ file\ changes.\ Please\ close\ files\ and\ processes\ and\ restart.\ You\ may\ encounter\ errors\ if\ you\ continue\ with\ this\ session.=No es posible supervisar los cambios en los archivos. Cierre los archivos y procesos y reinicie. Puede que se produzcan errores si continúa con esta sesión. %0\ contains\ the\ regular\ expression\ %1=%0 contiene la expresión regular %1 @@ -15,6 +20,7 @@ Unable\ to\ monitor\ file\ changes.\ Please\ close\ files\ and\ processes\ and\ %0/%1\ entries=%0/%1 entradas +Reveal\ in\ File\ Explorer=Mostrar en Explorador de Archivos %0\ matches\ the\ regular\ expression\ %1=%0 coincidencias con la Expresión Regular %1 @@ -49,6 +55,7 @@ The\ path\ need\ not\ be\ on\ the\ classpath\ of\ JabRef.=La ruta no debe estar Add\ a\ regular\ expression\ for\ the\ key\ pattern.=Añadir una expresión regular para el patrón clave. +Add\ entry\ manually=Añadir entrada manualmente Add\ selected\ entries\ to\ this\ group=Añadir entradas seleccionadas a este grupo @@ -166,6 +173,8 @@ Copy=Copiar Copy\ title=Copiar título Copy\ \\cite{citation\ key}=Copiar \\cite{citation key} +Copy\ citation\ (html)=Copiar cita (html) +Copy\ citation\ (text)=Copiar cita (texto) Copy\ citation\ key=Copiar clave de cita Copy\ citation\ key\ and\ link=Copiar clave de cita y enlace Copy\ citation\ key\ and\ title=Copiar clave y título de cita @@ -214,13 +223,17 @@ cut\ entries=Cortar entradas cut\ entry\ %0=corte de entrada %0 +DOI\ not\ found=DOI no encontrado Library\ encoding=Codificación de la biblioteca Library\ properties=Propiedades de la biblioteca +%0\ -\ Library\ properties=%0 - Propiedades de la biblioteca Default=Por defecto +Character\ encoding\ UTF-8\ is\ not\ supported.=La codificación de caracteres UTF-8 no restá soportada. +UTF-8\ could\ not\ be\ used\ to\ encode\ the\ following\ characters\:\ %0=UTF-8 no se pudo utilizar para codificar los siguientes caracteres\: %0 The\ chosen\ encoding\ '%0'\ could\ not\ encode\ the\ following\ characters\:=La codificación de caracteres '%' no puede codificar los siguientes caracteres\: Downloading=Descargando @@ -269,6 +282,7 @@ Downloaded\ website\ as\ an\ HTML\ file.=Se descargó el sitio web como un archi duplicate\ removal=eliminación de duplicados +Duplicate\ fields=Campos duplicados Duplicate\ string\ name=Nombre de cadena duplicado @@ -330,6 +344,7 @@ External\ file\ links=Enlaces a archivos externos External\ programs=Programas externos +Failed\ to\ import\ by\ ID=Error al importar por ID Field=Campo @@ -354,6 +369,7 @@ Filter=Filtro Filter\ groups=Filtros +Finished\ writing\ metadata\ for\ %0\ file\ (%1\ skipped,\ %2\ errors).=Escritura de metadatos para el archivo %0 finalizada (%1 omitidos, %2 errores). First\ select\ the\ entries\ you\ want\ keys\ to\ be\ generated\ for.=En primer lugar, seleccione las entradas para las que desea generar claves @@ -370,6 +386,7 @@ Formatter\ name=Nombre del formateador found\ in\ AUX\ file=encontrado en archivo AUX +Fulltext\ search=Búsqueda de texto completo Fulltext\ for=Texto completo de @@ -448,6 +465,9 @@ Include\ subgroups\:\ When\ selected,\ view\ entries\ contained\ in\ this\ group Independent\ group\:\ When\ selected,\ view\ only\ this\ group's\ entries=Grupo independiente\: ver sólo las entradas de este grupo cuando esté seleccionado. I\ Agree=Acepto +Indexing\ pdf\ files=Indexando archivos pdf +Indexing\ for\ %0=Indexando para %0 +%0\ of\ %1\ linked\ files\ added\ to\ the\ index=%0 de %1 archivos enlazados añadidos al índice Invalid\ citation\ key=La clave de cita no es válida @@ -461,6 +481,8 @@ JabRef\ requests\ recommendations\ from\ Mr.\ DLib,\ which\ is\ an\ external\ se JabRef\ Version\ (Required\ to\ ensure\ backwards\ compatibility\ with\ Mr.\ DLib's\ Web\ Service)=Versión JabRef (necesaria para asegurar la compatibilidad con el servicio web de Mr. DLib) Journal\ abbreviations=Abreviaturas de publicaciones +Journal\ lists\:=Listados de revistas\: +Remove\ journal\ '%0'=Eliminar revista '%0' Keep\ both=Mantener ambos @@ -496,6 +518,7 @@ Main\ file\ directory=Carpeta del archivo principal Manage\ custom\ exports=Administrar exportaciones personalizadas Manage\ custom\ imports=Administrar importaciones personalizadas +External\ file\ types=Tipos de archivos externos Mark\ new\ entries\ with\ owner\ name=Marcar nuevas entradas con nombre de propietario @@ -518,7 +541,9 @@ Moved\ group\ "%0".=Se ha movido el grupo "%0". Mr.\ DLib\ Privacy\ settings=Mr. DLib Configuración de la privacidad +No\ database\ is\ open=No hay ninguna base de datos abierta +We\ need\ a\ database\ to\ export\ from.\ Open\ one.=Necesitamos una base de datos desde la que exportar. Abrir una. No\ recommendations\ received\ from\ Mr.\ DLib\ for\ this\ entry.=No se han recibido recomendaciones del Mr. DLib para esta entrada. @@ -552,6 +577,7 @@ No\ journal\ names\ could\ be\ abbreviated.=No se pudieron abreviar nombres de r No\ journal\ names\ could\ be\ unabbreviated.=No se pudieron expandir nombres de revistas. +No\ DOI\ data\ exists=No existen datos DOI not=no @@ -678,16 +704,23 @@ Remove\ group=Eliminar grupo Remove\ group\ and\ subgroups=Eliminar grupo y subgrupos +Remove\ groups\ and\ subgroups=Eliminar grupos y subgrupos +Remove\ all\ selected\ groups\ and\ keep\ their\ subgroups?=¿Eliminar todos los grupos seleccionados y mantener sus subgrupos? +Remove\ group\ "%0"\ and\ keep\ its\ subgroups?=¿Eliminar grupo "%0" y mantener sus subgrupos? +Remove\ groups=Eliminar grupos +Removed\ all\ selected\ groups.=Eliminados todos los grupos seleccionados. Remove\ group\ "%0"\ and\ its\ subgroups?=¿Eliminar el grupo "%0" y sus subgrupos? Removed\ group\ "%0"\ and\ its\ subgroups.=Se ha eliminado el grupo "%0" y sus subgrupos. +Remove\ all\ selected\ groups\ and\ their\ subgroups?=¿Eliminar todos los grupos seleccionados y sus subgrupos? +Removed\ all\ selected\ groups\ and\ their\ subgroups.=Eliminados todos los grupos seleccionados y sus subgrupos. Remove\ link=Eliminar enlace @@ -719,6 +752,8 @@ Replaces\ Unicode\ ligatures\ with\ their\ expanded\ form=Reemplaza las ligadura Required\ fields=Campos requeridos +Do\ not\ resolve\ BibTeX\ strings=No resolver cadenas BibTeX +Resolve\ BibTeX\ strings\ for\ the\ following\ fields=Resolver las cadenas BibTeX para los siguientes campos resolved=resuelto @@ -726,11 +761,13 @@ Restart=Reiniciar Restart\ required=Reinicio requerido +Return\ to\ dialog=Volver al diálogo Review=Revisar Review\ changes=Revisar cambios Review\ Field\ Migration=Revisar campo de migración +Loading=Cargando Save=Guardar Save\ all\ finished.=Guardar todos los finalizados @@ -744,6 +781,7 @@ Save\ library\ as...=Guardar biblioteca como... Saving=Guardando Saving\ all\ libraries...=Guardando todas las bibliotecas... Saving\ library=Guardando biblioteca +Library\ saved=Biblioteca guardada Saved\ selected\ to\ '%0'.=Selección guardada en '%0'. Search=Buscar @@ -795,6 +833,12 @@ Size=Tamaño Skipped\ -\ No\ PDF\ linked=Omitido - No se enlazó PDF Skipped\ -\ PDF\ does\ not\ exist=Omitido - No existe el PDF +JabRef\ skipped\ the\ entry.=JabRef omitió la entrada. +Import\ error=Error al importar +Open\ library\ error=Error al abrir la biblioteca +Please\ check\ your\ library\ file\ for\ wrong\ syntax.=Por favor, comprueba errores de sintaxis en tu archivo de biblioteca. +SourceTab\ error=Error de SourceTab +User\ input\ via\ entry-editor\ in\ `{}bibtex\ source`\ tab\ led\ to\ failure.=Entrada de usuario a través del editor de entrada en la pestaña `{}bibtex source` condujo al error. Sort\ subgroups=Ordenar subgrupos @@ -883,13 +927,18 @@ Warning=Advertencia Warnings=Advertencias +Warning\:\ You\ added\ field\ "%0"\ twice.\ Only\ one\ will\ be\ kept.=Advertencia\: Añadiste el campo "%0" dos veces. Solo uno será utilizado. web\ link=enlace a web What\ do\ you\ want\ to\ do?=¿Qué desea hacer? Whatever\ option\ you\ choose,\ Mr.\ DLib\ may\ share\ its\ data\ with\ research\ partners\ to\ further\ improve\ recommendation\ quality\ as\ part\ of\ a\ 'living\ lab'.\ Mr.\ DLib\ may\ also\ release\ public\ datasets\ that\ may\ contain\ anonymized\ information\ about\ you\ and\ the\ recommendations\ (sensitive\ information\ such\ as\ metadata\ of\ your\ articles\ will\ be\ anonymised\ through\ e.g.\ hashing).\ Research\ partners\ are\ obliged\ to\ adhere\ to\ the\ same\ strict\ data\ protection\ policy\ as\ Mr.\ DLib.=Sea cual sea la opción que elija, Mr. DLib puede compartir sus datos con socios de investigación para mejorar aún más la calidad de las recomendaciones como parte de un "laboratorio vivo". Mr. DLib también puede publicar conjuntos de datos públicos que pueden contener información anónima sobre usted y las recomendaciones (la información confidencial como los metadatos de sus artículos será anonimizada a través de, por ejemplo, hashing). Los socios de investigación están obligados a adherirse a la misma estricta política de protección de datos que Mr. DLib. +Will\ write\ metadata\ to\ the\ PDFs\ linked\ from\ selected\ entries.=Escribirá metadatos en los PDFs enlazados desde entradas seleccionadas. +Write\ BibTeXEntry\ as\ metadata\ to\ PDF.=Escribir entrada BibTeX como metadatos XMP en el PDF. +Write\ metadata\ for\ all\ PDFs\ in\ current\ library?=¿Escribir metadatos para todos los PDFs de la biblioteca actual? +Writing\ metadata\ for\ selected\ entries...=Escribiendo metadatos para las entradas seleccionadas... Write\ BibTeXEntry\ as\ XMP\ metadata\ to\ PDF.=Escribe BibTeXEntry como metadatos XMP en los PDF. @@ -1587,6 +1636,8 @@ Issue\ report\ successful=Comunicación de problema satisfactoria Your\ issue\ was\ reported\ in\ your\ browser.=Su problema fue comunicado a través del navegador The\ log\ and\ exception\ information\ was\ copied\ to\ your\ clipboard.=La información de excepción y registro fue copiada a su portapapeles Please\ paste\ this\ information\ (with\ Ctrl+V)\ in\ the\ issue\ description.=Por favor, pegue esta información (con Ctrl+V) en la descripción del problema. +Last\ notification=Última notificación +Check\ the\ event\ log\ to\ see\ all\ notifications=Revisa el registro de eventos para ver todas las notificaciones Host=Host/Servidor Port=Puerto @@ -1732,6 +1783,7 @@ Delete\ '%0'=Eliminar '%0' Delete\ from\ disk=Eliminar del disco duro Remove\ from\ entry=Eliminar de la entrada There\ exists\ already\ a\ group\ with\ the\ same\ name.=Ya existe un grupo con el mismo nombre. +If\ you\ use\ it,\ it\ will\ inherit\ all\ entries\ from\ this\ other\ group.=Si lo utiliza, heredará todas las entradas de este otro grupo. Copy\ linked\ file=Copiar archivo enlazado Copy\ linked\ file\ to\ folder...=Copiar archivo enlazado a la carpeta... @@ -1787,6 +1839,7 @@ Could\ not\ connect\ to\ Vim\ server.\ Make\ sure\ that\ Vim\ is\ running\ with\ Could\ not\ connect\ to\ a\ running\ gnuserv\ process.\ Make\ sure\ that\ Emacs\ or\ XEmacs\ is\ running,\ and\ that\ the\ server\ has\ been\ started\ (by\ running\ the\ command\ 'server-start'/'gnuserv-start').=No se puede conectar con un proceso gnuserv en ejecución. Asegúrese de que Emacs o XEmacs se está ejecutando y de que el servidor ha sido iniciado (ejecutando el comando 'server-start'/'gnuserv-start'). Error\ pushing\ entries=Error al enviar entradas +Preamble=Preámbulo Markings=Marcados Use\ selected\ instance=Usar la instancia seleccionada @@ -1805,6 +1858,7 @@ Blog=Blog Check\ integrity=Verificar integridad Cleanup\ URL\ link=Limpiar un enlace URL Cleanup\ URL\ link\ by\ removing\ special\ symbols\ and\ extracting\ simple\ link=Limpiar un enlace URL eliminando los símbolos especiales y extrayendo un enlace simple +Copy\ DOI=Copiar DOI Copy\ DOI\ url=Copiar la url del DOI Development\ version=Versión de desarrollo Export\ selected\ entries=Exportar registros seleccionados @@ -1814,6 +1868,8 @@ JabRef\ resources=Recursos sobre JabRef Manage\ journal\ abbreviations=Administrar abreviaturas de publicaciones Manage\ protected\ terms=gestionar términos protegidos New\ entry\ from\ plain\ text=Nueva entrada desde texto sin formato +Import\ by\ ID=Importar por ID +Enter\ a\ valid\ ID=Introduzca un ID válido New\ sublibrary\ based\ on\ AUX\ file=Nueva subbiblioteca a partir de un archivo AUX Push\ entries\ to\ external\ application\ (%0)=Agregar registros a aplicación externa (%0) Quit=Salir @@ -1873,6 +1929,7 @@ Keyword\ separator=Separador de palabras clave Remove\ keyword=Eliminar palabra clave Are\ you\ sure\ you\ want\ to\ remove\ keyword\:\ "%0"?=¿Está seguro de que desea eliminar la palabra clave\: "%0"? Reset\ to\ default=Restablecer los valores por defecto +String\ constants=Constantes de cadena Export\ all\ entries=Exportar todas las entradas Generate\ citation\ keys=Generar claves de cita Manage\ field\ names\ &\ content=Gestionar nombres y contenido de los campos @@ -2002,6 +2059,7 @@ Please\ provide\ a\ valid\ aux\ file.=Por favor, proporcione un archivo AUX vál Keyword\ delimiter=Separador de palabras clave Hierarchical\ keyword\ delimiter=Separador de palabras clave jerárquicas Escape\ ampersands=Escape de ampersands +Escape\ dollar\ sign=Símbolo de escape dólar Hint\:\n\nTo\ search\ all\ fields\ for\ Smith,\ enter\:\nsmith\n\nTo\ search\ the\ field\ author\ for\ Smith\ and\ the\ field\ title\ for\ electrical,\ enter\:\nauthor\=Smith\ and\ title\=electrical=Consejo\:\n\nPara buscar Pedro en todos los campos, escriba\:\npedro\n\nPara buscar Pedro en el campo author y eléctrico en el campo title, escriba\:\nauthor\=Pedro and title\=eléctrico @@ -2211,7 +2269,11 @@ Reveal\ in\ file\ explorer=Revelar en el explorador de archivos Autolink\ files=Enlazar archivos automáticamente +Custom\ editor\ tabs=Pestañas de editor personalizadas +Custom\ export\ formats=Formatos de exportación personalizados +Custom\ import\ formats=Formatos de importación personalizados +No\ list\ enabled=No hay lista habilitada Protect\ selection=Proteger selección Customized\ preview\ style=Estilo de previsualización personalizado @@ -2238,7 +2300,17 @@ Regular\ expression=Expresión regular Error\ importing.\ See\ the\ error\ log\ for\ details.=Error al importar. Consulte el registro de errores para obtener detalles. - +Error\ from\ import\:\ %0=Error de importación\: %0 +Error\ reading\ PDF\ content\:\ %0=Error al leer el contenido PDF\: %0 +Importing\ bib\ entry=Importando entrada bibliográfica +Importing\ using\ extracted\ PDF\ data=Importación usando datos extraídos del PDF +No\ BibTeX\ data\ found.\ Creating\ empty\ entry\ with\ file\ link=No se encontraron datos BibTeX. Creando entrada vacía con enlace de archivo +No\ metadata\ found.\ Creating\ empty\ entry\ with\ file\ link=No se encontraron metadatos. Creando entrada vacía con enlace de archivo +Processing\ file\ %0=Procesando archivo %0 +Export\ selected=Exportar seleccionados + +Separate\ merged\ citations=Citas fusionadas separadas +Separate\ citations=Citas separadas Custom\ DOI\ URI=URI personalizado de DOI From 5338cfea5ac0132cbfece829bf92823c01ce9f7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 16:56:05 +0200 Subject: [PATCH 12/31] Bump appleboy/ssh-action from 0.1.4 to 0.1.5 (#9005) Bumps [appleboy/ssh-action](https://github.com/appleboy/ssh-action) from 0.1.4 to 0.1.5. - [Release notes](https://github.com/appleboy/ssh-action/releases) - [Commits](https://github.com/appleboy/ssh-action/compare/v0.1.4...v0.1.5) --- updated-dependencies: - dependency-name: appleboy/ssh-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cleanup_pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cleanup_pr.yml b/.github/workflows/cleanup_pr.yml index 63090aba74a..20747030207 100644 --- a/.github/workflows/cleanup_pr.yml +++ b/.github/workflows/cleanup_pr.yml @@ -27,7 +27,7 @@ jobs: echo "##[set-output name=branch;]$(echo ${{ github.event.pull_request.head.ref }})" - name: Delete folder on builds.jabref.org if: ${{ steps.checksecrets.outputs.secretspresent }} - uses: appleboy/ssh-action@v0.1.4 + uses: appleboy/ssh-action@v0.1.5 with: script: rm -rf /var/www/builds.jabref.org/www/${{ steps.extract_branch.outputs.branch }} || true host: build-upload.jabref.org From ffecadd25125da38bb751ebec2e8bc7f541bfad0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 16:56:15 +0200 Subject: [PATCH 13/31] Bump checkstyle from 10.3.1 to 10.3.2 (#9006) Bumps [checkstyle](https://github.com/checkstyle/checkstyle) from 10.3.1 to 10.3.2. - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-10.3.1...checkstyle-10.3.2) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3469f67e6ea..5579453fbc3 100644 --- a/build.gradle +++ b/build.gradle @@ -220,7 +220,7 @@ dependencies { testImplementation "org.testfx:testfx-junit5:4.0.16-alpha" testImplementation "org.hamcrest:hamcrest-library:2.2" - checkstyle 'com.puppycrawl.tools:checkstyle:10.3.1' + checkstyle 'com.puppycrawl.tools:checkstyle:10.3.2' // xjc needs the runtime as well for the ant task, otherwise it fails xjc group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: '3.0.2' xjc group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '3.0.2' From 91105e56c809adafc1c4812e9de4a4cfe23f99a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 17:41:33 +0200 Subject: [PATCH 14/31] Bump lucene-core from 9.2.0 to 9.3.0 (#9009) * Bump lucene-core from 9.2.0 to 9.3.0 Bumps lucene-core from 9.2.0 to 9.3.0. --- updated-dependencies: - dependency-name: org.apache.lucene:lucene-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * update lucence stuff Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Siedlerchr --- build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 5579453fbc3..483f21d7656 100644 --- a/build.gradle +++ b/build.gradle @@ -116,11 +116,11 @@ dependencies { implementation 'org.apache.pdfbox:fontbox:3.0.0-RC1' implementation 'org.apache.pdfbox:xmpbox:3.0.0-RC1' - implementation 'org.apache.lucene:lucene-core:9.2.0' - implementation 'org.apache.lucene:lucene-queryparser:9.2.0' - implementation 'org.apache.lucene:lucene-queries:9.2.0' - implementation 'org.apache.lucene:lucene-analysis-common:9.2.0' - implementation 'org.apache.lucene:lucene-highlighter:9.2.0' + implementation 'org.apache.lucene:lucene-core:9.3.0' + implementation 'org.apache.lucene:lucene-queryparser:9.3.0' + implementation 'org.apache.lucene:lucene-queries:9.3.0' + implementation 'org.apache.lucene:lucene-analysis-common:9.3.0' + implementation 'org.apache.lucene:lucene-highlighter:9.3.0' implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.9.0' implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' From 8389254c1de9955ef3feb1589b01e82e4323af99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:38:35 +0200 Subject: [PATCH 15/31] Bump junit-jupiter from 5.8.2 to 5.9.0 (#9012) * Bump junit-jupiter from 5.8.2 to 5.9.0 Bumps [junit-jupiter](https://github.com/junit-team/junit5) from 5.8.2 to 5.9.0. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.8.2...r5.9.0) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * remove private attribute from setup Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Siedlerchr --- build.gradle | 4 ++-- src/test/java/org/jabref/logic/texparser/LatexParserTest.java | 2 +- .../org/jabref/logic/texparser/TexBibEntriesResolverTest.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 483f21d7656..3c75afdba28 100644 --- a/build.gradle +++ b/build.gradle @@ -208,8 +208,8 @@ dependencies { implementation group: 'net.harawata', name: 'appdirs', version: '1.2.1' testImplementation 'io.github.classgraph:classgraph:4.8.149' - testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' - testImplementation 'org.junit.platform:junit-platform-launcher:1.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' + testImplementation 'org.junit.platform:junit-platform-launcher:1.9.0' testImplementation 'org.mockito:mockito-core:4.6.1' testImplementation 'org.xmlunit:xmlunit-core:2.9.0' diff --git a/src/test/java/org/jabref/logic/texparser/LatexParserTest.java b/src/test/java/org/jabref/logic/texparser/LatexParserTest.java index 95355121317..97929726abb 100644 --- a/src/test/java/org/jabref/logic/texparser/LatexParserTest.java +++ b/src/test/java/org/jabref/logic/texparser/LatexParserTest.java @@ -37,7 +37,7 @@ public class LatexParserTest { private BibDatabase database2; @BeforeEach - private void setUp() { + void setUp() { generalPreferences = mock(GeneralPreferences.class, Answers.RETURNS_DEEP_STUBS); importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); diff --git a/src/test/java/org/jabref/logic/texparser/TexBibEntriesResolverTest.java b/src/test/java/org/jabref/logic/texparser/TexBibEntriesResolverTest.java index ca90e30330f..a3870bafb72 100644 --- a/src/test/java/org/jabref/logic/texparser/TexBibEntriesResolverTest.java +++ b/src/test/java/org/jabref/logic/texparser/TexBibEntriesResolverTest.java @@ -38,7 +38,7 @@ public class TexBibEntriesResolverTest { private BibEntry bibEntry; @BeforeEach - private void setUp() { + void setUp() { generalPreferences = mock(GeneralPreferences.class, Answers.RETURNS_DEEP_STUBS); importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); From 02f51f7b2e395d0cf8d6eb0455e1a1e871a25fd0 Mon Sep 17 00:00:00 2001 From: Salieri <49811511+Ognimalf@users.noreply.github.com> Date: Tue, 2 Aug 2022 03:12:30 +0800 Subject: [PATCH 16/31] Support biblatex apa citation for legal entry types (#8966) Co-authored-by: Siedlerchr --- CHANGELOG.md | 1 + .../java/org/jabref/gui/EntryTypeView.java | 2 + .../importer/fileformat/BibTeXMLImporter.java | 2 +- .../importer/fileformat/BibtexParser.java | 4 +- .../importer/fileformat/CffImporter.java | 19 +++-- .../importer/fileformat/CopacImporter.java | 2 +- .../importer/fileformat/IsiImporter.java | 2 +- .../importer/fileformat/MedlineImporter.java | 4 +- .../logic/importer/util/MetaDataParser.java | 1 + .../model/entry/BibEntryTypesManager.java | 4 +- .../model/entry/field/BiblatexApaField.java | 82 +++++++++++++++++++ .../entry/field/BiblatexSoftwareField.java | 82 +++++++++++++++++++ .../model/entry/field/FieldFactory.java | 24 ++++-- .../jabref/model/entry/field/IEEEField.java | 2 +- .../model/entry/field/StandardField.java | 8 -- .../BiblatexAPAEntryTypeDefinitions.java | 43 ++++++++++ .../entry/types/BiblatexApaEntryType.java | 36 ++++++++ .../types/BiblatexSoftwareEntryType.java | 35 ++++++++ .../BiblatexSoftwareEntryTypeDefinitions.java | 41 +++++----- .../model/entry/types/EntryTypeFactory.java | 2 + .../model/entry/types/StandardEntryType.java | 5 +- .../OpenOfficeDocumentCreatorTest.java | 4 + .../importer/fileformat/CffImporterTest.java | 9 +- .../model/entry/BibEntryTypesManagerTest.java | 2 + .../model/entry/field/FieldFactoryTest.java | 13 +++ ...ficeCalcExportFormatContentSingleEntry.xml | 48 ----------- 26 files changed, 369 insertions(+), 108 deletions(-) create mode 100644 src/main/java/org/jabref/model/entry/field/BiblatexApaField.java create mode 100644 src/main/java/org/jabref/model/entry/field/BiblatexSoftwareField.java create mode 100644 src/main/java/org/jabref/model/entry/types/BiblatexAPAEntryTypeDefinitions.java create mode 100644 src/main/java/org/jabref/model/entry/types/BiblatexApaEntryType.java create mode 100644 src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryType.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf3434f2bb..aa8854dcfe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - JabRef now writes log files. Linux: `$home/.cache/jabref/logs/version`, Windows: `%APPDATA%\..\Local\harawata\jabref\version\logs`, Mac: `Users/.../Library/Logs/jabref/version` - We added an importer for Citavi backup files, support ".ctv5bak" and ".ctv6bak" file formats. [#8322](https://github.com/JabRef/jabref/issues/8322) - We added a feature to drag selected entries and drop them to other opened inactive library tabs [koppor521](https://github.com/koppor/jabref/issues/521). +- We added support for the [biblatex-apa](https://github.com/plk/biblatex-apa) legal entry types `Legislation`, `Legadminmaterial`, `Jurisdiction`, `Constitution` and `Legal` [#8931](https://github.com/JabRef/jabref/issues/8931) ### Changed diff --git a/src/main/java/org/jabref/gui/EntryTypeView.java b/src/main/java/org/jabref/gui/EntryTypeView.java index 10a95f3a5d0..165e1d7a380 100644 --- a/src/main/java/org/jabref/gui/EntryTypeView.java +++ b/src/main/java/org/jabref/gui/EntryTypeView.java @@ -28,6 +28,7 @@ import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.types.BiblatexAPAEntryTypeDefinitions; import org.jabref.model.entry.types.BiblatexEntryTypeDefinitions; import org.jabref.model.entry.types.BiblatexSoftwareEntryTypeDefinitions; import org.jabref.model.entry.types.BibtexEntryTypeDefinitions; @@ -157,6 +158,7 @@ public void initialize() { .filter(e -> !recommendedEntries.contains(e)) .collect(Collectors.toList()); otherEntries.addAll(BiblatexSoftwareEntryTypeDefinitions.ALL); + otherEntries.addAll(BiblatexAPAEntryTypeDefinitions.ALL); } else { recommendedEntries = BibtexEntryTypeDefinitions.RECOMMENDED; otherEntries = BibtexEntryTypeDefinitions.ALL diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibTeXMLImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/BibTeXMLImporter.java index 5fb3d8ed900..65301e2a7a1 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibTeXMLImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibTeXMLImporter.java @@ -184,7 +184,7 @@ private void parse(T entryType, Map fields) { } else if (isMethodToIgnore(method.getName())) { continue; } else if (method.getName().startsWith("get")) { - putIfValueNotNull(fields, FieldFactory.parseField(method.getName().replace("get", "")), (String) method.invoke(entryType)); + putIfValueNotNull(fields, FieldFactory.parseField(entryType, method.getName().replace("get", "")), (String) method.invoke(entryType)); } } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { LOGGER.error("Could not invoke method", e); diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index f3e6e7462f5..666ae01a7fb 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -387,8 +387,8 @@ private String purge(String context, String stringToPurge) { } // strip empty lines while ((runningIndex < indexOfAt) && - (context.charAt(runningIndex) == '\r' || - context.charAt(runningIndex) == '\n')) { + ((context.charAt(runningIndex) == '\r') || + (context.charAt(runningIndex) == '\n'))) { runningIndex++; } return context.substring(runningIndex); diff --git a/src/main/java/org/jabref/logic/importer/fileformat/CffImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/CffImporter.java index 2ff451e6dca..afbe5af657d 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/CffImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/CffImporter.java @@ -14,6 +14,7 @@ import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.BiblatexSoftwareField; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UnknownField; @@ -95,7 +96,7 @@ public ParserResult importDatabase(BufferedReader reader) throws IOException { StandardEntryType entryType = StandardEntryType.Software; // Map CFF fields to JabRef Fields - HashMap fieldMap = getFieldMappings(); + HashMap fieldMap = getFieldMappings(); for (Map.Entry property : citation.values.entrySet()) { if (fieldMap.containsKey(property.getKey())) { entryMap.put(fieldMap.get(property.getKey()), property.getValue()); @@ -120,7 +121,7 @@ public ParserResult importDatabase(BufferedReader reader) throws IOException { entryMap.put(StandardField.AUTHOR, authorStr); // Select DOI to keep - if (entryMap.get(StandardField.DOI) == null && citation.ids != null) { + if ((entryMap.get(StandardField.DOI) == null) && (citation.ids != null)) { List doiIds = citation.ids.stream() .filter(id -> id.type.equals("doi")) .collect(Collectors.toList()); @@ -137,14 +138,14 @@ public ParserResult importDatabase(BufferedReader reader) throws IOException { .collect(Collectors.toList()); if (swhIds.size() == 1) { - entryMap.put(StandardField.SWHID, swhIds.get(0)); + entryMap.put(BiblatexSoftwareField.SWHID, swhIds.get(0)); } else if (swhIds.size() > 1) { List relSwhIds = swhIds.stream() .filter(id -> id.split(":").length > 3) // quick filter for invalid swhids .filter(id -> id.split(":")[2].equals("rel")) .collect(Collectors.toList()); if (relSwhIds.size() == 1) { - entryMap.put(StandardField.SWHID, relSwhIds.get(0)); + entryMap.put(BiblatexSoftwareField.SWHID, relSwhIds.get(0)); } } } @@ -166,19 +167,19 @@ public boolean isRecognizedFormat(BufferedReader reader) throws IOException { try { citation = mapper.readValue(reader, CffFormat.class); - return citation != null && citation.values.get("title") != null; + return (citation != null) && (citation.values.get("title") != null); } catch (IOException e) { return false; } } - private HashMap getFieldMappings() { - HashMap fieldMappings = new HashMap<>(); + private HashMap getFieldMappings() { + HashMap fieldMappings = new HashMap<>(); fieldMappings.put("title", StandardField.TITLE); fieldMappings.put("version", StandardField.VERSION); fieldMappings.put("doi", StandardField.DOI); - fieldMappings.put("license", StandardField.LICENSE); - fieldMappings.put("repository", StandardField.REPOSITORY); + fieldMappings.put("license", BiblatexSoftwareField.LICENSE); + fieldMappings.put("repository", BiblatexSoftwareField.REPOSITORY); fieldMappings.put("url", StandardField.URL); fieldMappings.put("abstract", StandardField.ABSTRACT); fieldMappings.put("message", StandardField.COMMENT); diff --git a/src/main/java/org/jabref/logic/importer/fileformat/CopacImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/CopacImporter.java index ab857becd0a..ab638ae4cf3 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/CopacImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/CopacImporter.java @@ -137,7 +137,7 @@ public ParserResult importDatabase(BufferedReader reader) throws IOException { } else if ("DT- ".equals(code)) { setOrAppend(b, new UnknownField("documenttype"), line.substring(4).trim(), ", "); } else { - setOrAppend(b, FieldFactory.parseField(code.substring(0, 2)), line.substring(4).trim(), ", "); + setOrAppend(b, FieldFactory.parseField(StandardEntryType.Book, line.substring(0, 2)), line.substring(4).trim(), ", "); } } results.add(b); diff --git a/src/main/java/org/jabref/logic/importer/fileformat/IsiImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/IsiImporter.java index b502977f716..2845756b770 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/IsiImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/IsiImporter.java @@ -281,7 +281,7 @@ public ParserResult importDatabase(BufferedReader reader) throws IOException { if ("ER".equals(beg) || "EF".equals(beg) || "VR".equals(beg) || "FN".equals(beg)) { continue; } - hm.put(FieldFactory.parseField(beg), value); + hm.put(FieldFactory.parseField(type, beg), value); } } diff --git a/src/main/java/org/jabref/logic/importer/fileformat/MedlineImporter.java b/src/main/java/org/jabref/logic/importer/fileformat/MedlineImporter.java index 392fad9772b..b9e9eac39d4 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/MedlineImporter.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/MedlineImporter.java @@ -425,7 +425,7 @@ private void addArticleIdList(Map fields, ArticleIdList articleId if ("pubmed".equals(id.getIdType())) { fields.put(StandardField.PMID, id.getContent()); } else { - fields.put(FieldFactory.parseField(id.getIdType()), id.getContent()); + fields.put(FieldFactory.parseField(StandardEntryType.Article, id.getIdType()), id.getContent()); } } } @@ -499,7 +499,7 @@ private void addKeyWords(Map fields, List allKeyword private void addOtherId(Map fields, List otherID) { for (OtherID id : otherID) { if ((id.getSource() != null) && (id.getContent() != null)) { - fields.put(FieldFactory.parseField(id.getSource()), id.getContent()); + fields.put(FieldFactory.parseField(StandardEntryType.Article, id.getSource()), id.getContent()); } } } diff --git a/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java b/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java index eb1e905adca..5f3d5e29c52 100644 --- a/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java +++ b/src/main/java/org/jabref/logic/importer/util/MetaDataParser.java @@ -65,6 +65,7 @@ public MetaData parse(MetaData metaData, Map data, Character key String user = entry.getKey().substring(MetaData.FILE_DIRECTORY.length() + 1); metaData.setUserFileDirectory(user, getSingleItem(value)); } else if (entry.getKey().startsWith(MetaData.SELECTOR_META_PREFIX)) { + // edge case, it might be one special field e.g. article from biblatex-apa, but we can't distinguish this from any other field and rather prefer to handle it as UnknownField metaData.addContentSelector(ContentSelectors.parse(FieldFactory.parseField(entry.getKey().substring(MetaData.SELECTOR_META_PREFIX.length())), StringUtil.unquote(entry.getValue(), MetaData.ESCAPE_CHARACTER))); } else if (entry.getKey().startsWith(MetaData.FILE_DIRECTORY + "Latex-")) { // The user name comes directly after "FILE_DIRECTORYLatex-" diff --git a/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java b/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java index b737596abd6..836cd39dac8 100644 --- a/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java +++ b/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java @@ -11,6 +11,7 @@ import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.field.BibField; import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.types.BiblatexAPAEntryTypeDefinitions; import org.jabref.model.entry.types.BiblatexEntryTypeDefinitions; import org.jabref.model.entry.types.BiblatexSoftwareEntryTypeDefinitions; import org.jabref.model.entry.types.BibtexEntryTypeDefinitions; @@ -21,7 +22,7 @@ public class BibEntryTypesManager { public static final String ENTRYTYPE_FLAG = "jabref-entrytype: "; private final InternalEntryTypes BIBTEX = new InternalEntryTypes(Stream.concat(BibtexEntryTypeDefinitions.ALL.stream(), IEEETranEntryTypeDefinitions.ALL.stream()).collect(Collectors.toList())); - private final InternalEntryTypes BIBLATEX = new InternalEntryTypes(Stream.concat(BiblatexEntryTypeDefinitions.ALL.stream(), BiblatexSoftwareEntryTypeDefinitions.ALL.stream()).collect(Collectors.toList())); + private final InternalEntryTypes BIBLATEX = new InternalEntryTypes(Stream.concat(BiblatexEntryTypeDefinitions.ALL.stream(), Stream.concat(BiblatexSoftwareEntryTypeDefinitions.ALL.stream(), BiblatexAPAEntryTypeDefinitions.ALL.stream())).collect(Collectors.toList())); public BibEntryTypesManager() { } @@ -99,6 +100,7 @@ public List getAllCustomTypes(BibDatabaseMode mode) { return customizedTypes.stream() .filter(entryType -> BiblatexEntryTypeDefinitions.ALL.stream().noneMatch(biblatexType -> biblatexType.getType().equals(entryType.getType()))) .filter(entryType -> BiblatexSoftwareEntryTypeDefinitions.ALL.stream().noneMatch(biblatexSoftware -> biblatexSoftware.getType().equals(entryType.getType()))) + .filter(entryType -> BiblatexAPAEntryTypeDefinitions.ALL.stream().noneMatch(biblatexAPA -> biblatexAPA.getType().equals(entryType.getType()))) .collect(Collectors.toList()); } } diff --git a/src/main/java/org/jabref/model/entry/field/BiblatexApaField.java b/src/main/java/org/jabref/model/entry/field/BiblatexApaField.java new file mode 100644 index 00000000000..b2938963efe --- /dev/null +++ b/src/main/java/org/jabref/model/entry/field/BiblatexApaField.java @@ -0,0 +1,82 @@ +package org.jabref.model.entry.field; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Optional; +import java.util.Set; + +import org.jabref.model.entry.types.BiblatexApaEntryType; + +public enum BiblatexApaField implements Field { + + AMENDMENT("amendment"), + ARTICLE("article"), + CITATION("citation"), + CITATION_CITEORG("citation_citeorg"), + CITATION_CITEDATE("citation_citedate", FieldProperty.DATE), + CITATION_CITEINFO("citation_citeinfo"), + SECTION("section", FieldProperty.NUMERIC), + SOURCE("source"); + + private final String name; + private final String displayName; + private final Set properties; + + BiblatexApaField(String name) { + this.name = name; + this.displayName = null; + this.properties = EnumSet.noneOf(FieldProperty.class); + } + + BiblatexApaField(String name, String displayName) { + this.name = name; + this.displayName = displayName; + this.properties = EnumSet.noneOf(FieldProperty.class); + } + + BiblatexApaField(String name, String displayName, FieldProperty first, FieldProperty... rest) { + this.name = name; + this.displayName = displayName; + this.properties = EnumSet.of(first, rest); + } + + BiblatexApaField(String name, FieldProperty first, FieldProperty... rest) { + this.name = name; + this.displayName = null; + this.properties = EnumSet.of(first, rest); + } + + public static Optional fromName(T type, String name) { + if (!(type instanceof BiblatexApaEntryType)) { + return Optional.empty(); + } + return Arrays.stream(BiblatexApaField.values()) + .filter(field -> field.getName().equalsIgnoreCase(name)) + .findAny(); + } + + @Override + public Set getProperties() { + return Collections.unmodifiableSet(properties); + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isStandardField() { + return false; + } + + @Override + public String getDisplayName() { + if (displayName == null) { + return Field.super.getDisplayName(); + } else { + return displayName; + } + } +} diff --git a/src/main/java/org/jabref/model/entry/field/BiblatexSoftwareField.java b/src/main/java/org/jabref/model/entry/field/BiblatexSoftwareField.java new file mode 100644 index 00000000000..fc3bb4dcc48 --- /dev/null +++ b/src/main/java/org/jabref/model/entry/field/BiblatexSoftwareField.java @@ -0,0 +1,82 @@ +package org.jabref.model.entry.field; + +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Optional; +import java.util.Set; + +import org.jabref.model.entry.types.BiblatexSoftwareEntryType; + +public enum BiblatexSoftwareField implements Field { + + HALID("hal_id"), + HALVERSION("hal_version"), + INTRODUCEDIN("introducedin"), + LICENSE("license"), + RELATEDTYPE("relatedtype"), + RELATEDSTRING("relatedstring"), + REPOSITORY("repository"), + SWHID("swhid"); + + private final String name; + private final String displayName; + private final Set properties; + + BiblatexSoftwareField(String name) { + this.name = name; + this.displayName = null; + this.properties = EnumSet.noneOf(FieldProperty.class); + } + + BiblatexSoftwareField(String name, String displayName) { + this.name = name; + this.displayName = displayName; + this.properties = EnumSet.noneOf(FieldProperty.class); + } + + BiblatexSoftwareField(String name, String displayName, FieldProperty first, FieldProperty... rest) { + this.name = name; + this.displayName = displayName; + this.properties = EnumSet.of(first, rest); + } + + BiblatexSoftwareField(String name, FieldProperty first, FieldProperty... rest) { + this.name = name; + this.displayName = null; + this.properties = EnumSet.of(first, rest); + } + + public static Optional fromName(T type, String name) { + if (!(type instanceof BiblatexSoftwareEntryType)) { + return Optional.empty(); + } + return Arrays.stream(BiblatexSoftwareField.values()) + .filter(field -> field.getName().equalsIgnoreCase(name)) + .findAny(); + } + + @Override + public Set getProperties() { + return Collections.unmodifiableSet(properties); + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isStandardField() { + return false; + } + + @Override + public String getDisplayName() { + if (displayName == null) { + return Field.super.getDisplayName(); + } else { + return displayName; + } + } +} diff --git a/src/main/java/org/jabref/model/entry/field/FieldFactory.java b/src/main/java/org/jabref/model/entry/field/FieldFactory.java index b1c2f3b99b9..07a34b039d3 100644 --- a/src/main/java/org/jabref/model/entry/field/FieldFactory.java +++ b/src/main/java/org/jabref/model/entry/field/FieldFactory.java @@ -73,13 +73,23 @@ public static String serializeFieldsList(Collection fields) { .collect(Collectors.joining(DELIMITER)); } + public static Field parseField(T type, String fieldName) { + return OptionalUtil.orElse( + OptionalUtil.orElse( + OptionalUtil.orElse( + OptionalUtil.orElse( + OptionalUtil.orElse( + InternalField.fromName(fieldName), + StandardField.fromName(fieldName)), + SpecialField.fromName(fieldName)), + IEEEField.fromName(fieldName)), + BiblatexSoftwareField.fromName(type, fieldName)), + BiblatexApaField.fromName(type, fieldName)) + .orElse(new UnknownField(fieldName)); + } + public static Field parseField(String fieldName) { - return OptionalUtil.orElse(OptionalUtil.orElse(OptionalUtil.orElse( - InternalField.fromName(fieldName), - StandardField.fromName(fieldName)), - SpecialField.fromName(fieldName)), - IEEEField.fromName(fieldName)) - .orElse(new UnknownField(fieldName)); + return parseField(null, fieldName); } public static Set getKeyFields() { @@ -138,6 +148,8 @@ private static Set getFieldsFiltered(Predicate selector) { private static Set getAllFields() { Set fields = new HashSet<>(); + fields.addAll(EnumSet.allOf(BiblatexApaField.class)); + fields.addAll(EnumSet.allOf(BiblatexSoftwareField.class)); fields.addAll(EnumSet.allOf(IEEEField.class)); fields.addAll(EnumSet.allOf(InternalField.class)); fields.addAll(EnumSet.allOf(SpecialField.class)); diff --git a/src/main/java/org/jabref/model/entry/field/IEEEField.java b/src/main/java/org/jabref/model/entry/field/IEEEField.java index d00bec02eee..f56ce79ea99 100644 --- a/src/main/java/org/jabref/model/entry/field/IEEEField.java +++ b/src/main/java/org/jabref/model/entry/field/IEEEField.java @@ -31,7 +31,7 @@ public enum IEEEField implements Field { this.properties = EnumSet.of(first, rest); } - public static Optional fromName(String name) { + public static Optional fromName(String name) { return Arrays.stream(IEEEField.values()) .filter(field -> field.getName().equalsIgnoreCase(name)) .findAny(); diff --git a/src/main/java/org/jabref/model/entry/field/StandardField.java b/src/main/java/org/jabref/model/entry/field/StandardField.java index ef3646802e0..bcf9b710b7d 100644 --- a/src/main/java/org/jabref/model/entry/field/StandardField.java +++ b/src/main/java/org/jabref/model/entry/field/StandardField.java @@ -59,14 +59,11 @@ public enum StandardField implements Field { FOREWORD("foreword", FieldProperty.PERSON_NAMES), FOLDER("folder"), GENDER("gender", FieldProperty.GENDER), - HALID("hal_id"), - HALVERSION("hal_version"), HOLDER("holder", FieldProperty.PERSON_NAMES), HOWPUBLISHED("howpublished"), IDS("ids", FieldProperty.MULTIPLE_ENTRY_LINK), INSTITUTION("institution"), INTRODUCTION("introduction", FieldProperty.PERSON_NAMES), - INTRODUCEDIN("introducedin"), ISBN("isbn", "ISBN", FieldProperty.ISBN), ISRN("isrn", "ISRN"), ISSN("issn", "ISSN"), @@ -81,7 +78,6 @@ public enum StandardField implements Field { LANGUAGE("language", FieldProperty.LANGUAGE), LABEL("label"), LIBRARY("library"), - LICENSE("license"), LOCATION("location"), MAINSUBTITLE("mainsubtitle", FieldProperty.BOOK_NAME), MAINTITLE("maintitle", FieldProperty.BOOK_NAME), @@ -106,10 +102,7 @@ public enum StandardField implements Field { PUBSTATE("pubstate", FieldProperty.PUBLICATION_STATE), PRIMARYCLASS("primaryclass"), RELATED("related", FieldProperty.MULTIPLE_ENTRY_LINK), - RELATEDTYPE("relatedtype"), - RELATEDSTRING("relatedstring"), REPORTNO("reportno"), - REPOSITORY("repository"), REVIEW("review"), REVISION("revision"), SCHOOL("school"), @@ -120,7 +113,6 @@ public enum StandardField implements Field { SORTKEY("sortkey"), SORTNAME("sortname", FieldProperty.PERSON_NAMES), SUBTITLE("subtitle"), - SWHID("swhid"), TITLE("title"), TITLEADDON("titleaddon"), TRANSLATOR("translator", FieldProperty.PERSON_NAMES), diff --git a/src/main/java/org/jabref/model/entry/types/BiblatexAPAEntryTypeDefinitions.java b/src/main/java/org/jabref/model/entry/types/BiblatexAPAEntryTypeDefinitions.java new file mode 100644 index 00000000000..f5c0b89b0be --- /dev/null +++ b/src/main/java/org/jabref/model/entry/types/BiblatexAPAEntryTypeDefinitions.java @@ -0,0 +1,43 @@ +package org.jabref.model.entry.types; + +import java.util.Arrays; +import java.util.List; + +import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.BibEntryTypeBuilder; +import org.jabref.model.entry.field.BiblatexApaField; +import org.jabref.model.entry.field.StandardField; + +public class BiblatexAPAEntryTypeDefinitions { + + private static final BibEntryType JURISDICTION = new BibEntryTypeBuilder() + .withType(BiblatexApaEntryType.Jurisdiction) + .withImportantFields(StandardField.ORGANIZATION, BiblatexApaField.CITATION_CITEORG, BiblatexApaField.CITATION_CITEDATE, BiblatexApaField.CITATION_CITEDATE, StandardField.ORIGDATE) + .withRequiredFields(StandardField.TITLE, BiblatexApaField.CITATION, BiblatexApaField.CITATION_CITEINFO, StandardField.URL, StandardField.DATE) + .build(); + + private static final BibEntryType LEGISLATION = new BibEntryTypeBuilder() + .withType(BiblatexApaEntryType.Legislation) + .withImportantFields(StandardField.TITLEADDON, StandardField.ORIGDATE) + .withRequiredFields(StandardField.TITLE, StandardField.LOCATION, StandardField.URL, StandardField.DATE) + .build(); + + private static final BibEntryType LEGADMINMATERIAL = new BibEntryTypeBuilder() + .withType(BiblatexApaEntryType.Legadminmaterial) + .withImportantFields(StandardField.NUMBER, StandardField.SHORTTITLE, StandardField.NOTE, StandardField.KEYWORDS) + .withRequiredFields(StandardField.TITLE, BiblatexApaField.CITATION, StandardField.URL, StandardField.DATE) + .build(); + + private static final BibEntryType CONSTITUTION = new BibEntryTypeBuilder() + .withType(BiblatexApaEntryType.Constitution) + .withImportantFields(BiblatexApaField.ARTICLE, BiblatexApaField.AMENDMENT, StandardField.EVENTDATE, StandardField.KEYWORDS, StandardField.PART, BiblatexApaField.SECTION) + .withRequiredFields(BiblatexApaField.SOURCE, StandardField.TYPE) + .build(); + + private static final BibEntryType LEGAL = new BibEntryTypeBuilder() + .withType(BiblatexApaEntryType.Legal) + .withRequiredFields(StandardField.TITLE, StandardField.DATE, StandardField.URI, StandardField.KEYWORDS, StandardField.PART, BiblatexApaField.SECTION) + .build(); + + public static final List ALL = Arrays.asList(JURISDICTION, LEGISLATION, LEGADMINMATERIAL, CONSTITUTION, LEGAL); +} diff --git a/src/main/java/org/jabref/model/entry/types/BiblatexApaEntryType.java b/src/main/java/org/jabref/model/entry/types/BiblatexApaEntryType.java new file mode 100644 index 00000000000..6a1d8d3abe2 --- /dev/null +++ b/src/main/java/org/jabref/model/entry/types/BiblatexApaEntryType.java @@ -0,0 +1,36 @@ +package org.jabref.model.entry.types; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Optional; + +public enum BiblatexApaEntryType implements EntryType { + + Legislation("Legislation"), + Legadminmaterial("Legadminmaterial"), + Jurisdiction("Jurisdiction"), + Constitution("Constitution"), + Legal("Legal"); + + private final String displayName; + + BiblatexApaEntryType(String displayName) { + this.displayName = displayName; + } + + @Override + public String getName() { + return displayName.toLowerCase(Locale.ENGLISH); + } + + @Override + public String getDisplayName() { + return displayName; + } + + public static Optional fromName(String name) { + return Arrays.stream(BiblatexApaEntryType.values()) + .filter(field -> field.getName().equalsIgnoreCase(name)) + .findAny(); + } +} diff --git a/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryType.java b/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryType.java new file mode 100644 index 00000000000..041c48e592a --- /dev/null +++ b/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryType.java @@ -0,0 +1,35 @@ +package org.jabref.model.entry.types; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Optional; + +public enum BiblatexSoftwareEntryType implements EntryType { + + Dataset("Dataset"), + SoftwareVersion("SoftwareVersion"), + SoftwareModule("SoftwareModule"), + CodeFragment("CodeFragment"); + + private final String displayName; + + BiblatexSoftwareEntryType(String displayName) { + this.displayName = displayName; + } + + public static Optional fromName(String name) { + return Arrays.stream(BiblatexSoftwareEntryType.values()) + .filter(field -> field.getName().equalsIgnoreCase(name)) + .findAny(); + } + + @Override + public String getName() { + return displayName.toLowerCase(Locale.ENGLISH); + } + + @Override + public String getDisplayName() { + return displayName; + } +} diff --git a/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitions.java b/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitions.java index 0fc48e6d904..6a2e7f0851e 100644 --- a/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitions.java +++ b/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitions.java @@ -5,6 +5,7 @@ import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibEntryTypeBuilder; +import org.jabref.model.entry.field.BiblatexSoftwareField; import org.jabref.model.entry.field.OrFields; import org.jabref.model.entry.field.StandardField; @@ -12,40 +13,40 @@ public class BiblatexSoftwareEntryTypeDefinitions { private static final BibEntryType SOFTWARE = new BibEntryTypeBuilder() .withType(StandardEntryType.Software) .withImportantFields(StandardField.DATE, StandardField.DOI, StandardField.EPRINTTYPE, StandardField.EPRINTCLASS, StandardField.EPRINT, - StandardField.EDITOR, StandardField.FILE, StandardField.HALID, StandardField.HALVERSION, StandardField.INSTITUTION, StandardField.INTRODUCEDIN, - StandardField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.RELATED, - StandardField.RELATEDSTRING, StandardField.REPOSITORY, StandardField.SWHID, StandardField.URLDATE, StandardField.VERSION) + StandardField.EDITOR, StandardField.FILE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, + BiblatexSoftwareField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.RELATED, + BiblatexSoftwareField.RELATEDSTRING, BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.URLDATE, StandardField.VERSION) .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.URL, StandardField.VERSION, StandardField.YEAR) .build(); private static final BibEntryType SOFTWAREVERSION = new BibEntryTypeBuilder() - .withType(StandardEntryType.SoftwareVersion) - .withImportantFields(StandardField.DATE, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.HALID, StandardField.HALVERSION, - StandardField.INSTITUTION, StandardField.INTRODUCEDIN, StandardField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, - StandardField.PUBLISHER, StandardField.RELATED, StandardField.RELATEDTYPE, StandardField.RELATEDSTRING, - StandardField.REPOSITORY, StandardField.SWHID, StandardField.SUBTITLE, StandardField.URLDATE) + .withType(BiblatexSoftwareEntryType.SoftwareVersion) + .withImportantFields(StandardField.DATE, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, + StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, BiblatexSoftwareField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, + StandardField.PUBLISHER, StandardField.RELATED, BiblatexSoftwareField.RELATEDTYPE, BiblatexSoftwareField.RELATEDSTRING, + BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.SUBTITLE, StandardField.URLDATE) .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.URL, StandardField.YEAR, StandardField.VERSION) - .withDetailFields(StandardField.DATE, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.HALID, StandardField.HALVERSION, - StandardField.INSTITUTION, StandardField.INTRODUCEDIN, StandardField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, - StandardField.PUBLISHER, StandardField.RELATED, StandardField.RELATEDTYPE, StandardField.RELATEDSTRING, - StandardField.REPOSITORY, StandardField.SWHID, StandardField.SUBTITLE, StandardField.URLDATE) + .withDetailFields(StandardField.DATE, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, + StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, BiblatexSoftwareField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, + StandardField.PUBLISHER, StandardField.RELATED, BiblatexSoftwareField.RELATEDTYPE, BiblatexSoftwareField.RELATEDSTRING, + BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.SUBTITLE, StandardField.URLDATE) .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.URL, StandardField.YEAR) .build(); private static final BibEntryType SOFTWAREMODULE = new BibEntryTypeBuilder() - .withType(StandardEntryType.SoftwareModule) + .withType(BiblatexSoftwareEntryType.SoftwareModule) .withImportantFields(StandardField.DATE, StandardField.DOI, StandardField.EPRINTTYPE, StandardField.EPRINTCLASS, StandardField.EPRINT, - StandardField.EDITOR, StandardField.FILE, StandardField.HALID, StandardField.HALVERSION, StandardField.INSTITUTION, StandardField.INTRODUCEDIN, - StandardField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.RELATED, - StandardField.RELATEDSTRING, StandardField.REPOSITORY, StandardField.SWHID, StandardField.URLDATE, StandardField.VERSION) + StandardField.EDITOR, StandardField.FILE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, + BiblatexSoftwareField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.RELATED, + BiblatexSoftwareField.RELATEDSTRING, BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.URLDATE, StandardField.VERSION) .withRequiredFields(StandardField.AUTHOR, StandardField.SUBTITLE, StandardField.URL, StandardField.YEAR) .build(); private static final BibEntryType CODEFRAGMENT = new BibEntryTypeBuilder() - .withType(StandardEntryType.CodeFragment) + .withType(BiblatexSoftwareEntryType.CodeFragment) .withImportantFields(StandardField.DATE, StandardField.DOI, StandardField.EPRINTTYPE, StandardField.EPRINTCLASS, StandardField.EPRINT, - StandardField.EDITOR, StandardField.FILE, StandardField.HALID, StandardField.HALVERSION, StandardField.INSTITUTION, StandardField.INTRODUCEDIN, - StandardField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.RELATED, - StandardField.RELATEDSTRING, StandardField.REPOSITORY, StandardField.SWHID, StandardField.URLDATE, StandardField.VERSION) + StandardField.EDITOR, StandardField.FILE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, + BiblatexSoftwareField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.RELATED, + BiblatexSoftwareField.RELATEDSTRING, BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.URLDATE, StandardField.VERSION) .withRequiredFields(StandardField.URL) .build(); diff --git a/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java b/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java index 1c94f6dd9a0..6b40a56c4f4 100644 --- a/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java +++ b/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java @@ -49,6 +49,8 @@ private static boolean isBiblatex(EntryType type) { public static EntryType parse(String typeName) { List types = new ArrayList<>(Arrays.asList(StandardEntryType.values())); types.addAll(Arrays.asList(IEEETranEntryType.values())); + types.addAll(Arrays.asList(BiblatexSoftwareEntryType.values())); + types.addAll(Arrays.asList(BiblatexApaEntryType.values())); types.addAll(Arrays.asList(SystematicLiteratureReviewStudyEntryType.values())); return types.stream().filter(type -> type.getName().equals(typeName.toLowerCase(Locale.ENGLISH))).findFirst().orElse(new UnknownEntryType(typeName)); diff --git a/src/main/java/org/jabref/model/entry/types/StandardEntryType.java b/src/main/java/org/jabref/model/entry/types/StandardEntryType.java index fb2922e61a0..6f6f91ac6a5 100644 --- a/src/main/java/org/jabref/model/entry/types/StandardEntryType.java +++ b/src/main/java/org/jabref/model/entry/types/StandardEntryType.java @@ -36,10 +36,7 @@ public enum StandardEntryType implements EntryType { Thesis("Thesis"), WWW("WWW"), Software("Software"), - Dataset("Dataset"), - SoftwareVersion("SoftwareVersion"), - SoftwareModule("SoftwareModule"), - CodeFragment("CodeFragment"); + Dataset("Dataset"); private final String displayName; diff --git a/src/test/java/org/jabref/logic/exporter/OpenOfficeDocumentCreatorTest.java b/src/test/java/org/jabref/logic/exporter/OpenOfficeDocumentCreatorTest.java index 1c5ebf0cbc8..886398c7725 100644 --- a/src/test/java/org/jabref/logic/exporter/OpenOfficeDocumentCreatorTest.java +++ b/src/test/java/org/jabref/logic/exporter/OpenOfficeDocumentCreatorTest.java @@ -90,6 +90,10 @@ void testPerformExportForSingleEntry(@TempDir Path testFolder) throws Exception Input.Builder control = Input.from(Files.newInputStream(xmlFile)); Input.Builder test = Input.from(Files.newInputStream(contentXmlPath)); + // for debugging purposes + // Path testPath = xmlFile.resolveSibling("test.xml"); + // Files.copy(Files.newInputStream(contentXmlPath), testPath, StandardCopyOption.REPLACE_EXISTING); + assertThat(test, CompareMatcher.isSimilarTo(control) .normalizeWhitespace() .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)).throwComparisonFailure()); diff --git a/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java index 38fa42f948d..cc3e27cae1f 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java @@ -8,6 +8,7 @@ import org.jabref.logic.util.StandardFileType; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.BiblatexSoftwareField; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UnknownField; import org.jabref.model.entry.types.StandardEntryType; @@ -93,7 +94,7 @@ public void testImportEntriesSwhIdSelect1() throws IOException, URISyntaxExcepti List bibEntries = importer.importDatabase(file).getDatabase().getEntries(); BibEntry entry = bibEntries.get(0); - BibEntry expected = getPopulatedEntry().withField(StandardField.SWHID, "swh:1:rel:22ece559cc7cc2364edc5e5593d63ae8bd229f9f"); + BibEntry expected = getPopulatedEntry().withField(BiblatexSoftwareField.SWHID, "swh:1:rel:22ece559cc7cc2364edc5e5593d63ae8bd229f9f"); assertEquals(entry, expected); } @@ -104,7 +105,7 @@ public void testImportEntriesSwhIdSelect2() throws IOException, URISyntaxExcepti List bibEntries = importer.importDatabase(file).getDatabase().getEntries(); BibEntry entry = bibEntries.get(0); - BibEntry expected = getPopulatedEntry().withField(StandardField.SWHID, "swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2"); + BibEntry expected = getPopulatedEntry().withField(BiblatexSoftwareField.SWHID, "swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2"); assertEquals(entry, expected); } @@ -150,12 +151,12 @@ public BibEntry getPopulatedEntry() { entry.setField(StandardField.AUTHOR, "Joe van Smith and Bob Jones, Jr."); entry.setField(StandardField.TITLE, "Test"); entry.setField(StandardField.URL, "www.google.com"); - entry.setField(StandardField.REPOSITORY, "www.github.com"); + entry.setField(BiblatexSoftwareField.REPOSITORY, "www.github.com"); entry.setField(StandardField.DOI, "10.0000/TEST"); entry.setField(StandardField.DATE, "2000-07-02"); entry.setField(StandardField.COMMENT, "Test entry."); entry.setField(StandardField.ABSTRACT, "Test abstract."); - entry.setField(StandardField.LICENSE, "MIT"); + entry.setField(BiblatexSoftwareField.LICENSE, "MIT"); entry.setField(StandardField.VERSION, "1.0"); return entry; diff --git a/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java b/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java index 48b47bc4f32..1e87c87abd7 100644 --- a/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java +++ b/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java @@ -13,6 +13,7 @@ import org.jabref.model.entry.field.OrFields; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.field.UnknownField; +import org.jabref.model.entry.types.BiblatexAPAEntryTypeDefinitions; import org.jabref.model.entry.types.BiblatexEntryTypeDefinitions; import org.jabref.model.entry.types.BiblatexSoftwareEntryTypeDefinitions; import org.jabref.model.entry.types.BibtexEntryTypeDefinitions; @@ -74,6 +75,7 @@ void allTypesBibtexAreCorrect() { void allTypesBiblatexAreCorrect() { TreeSet defaultTypes = new TreeSet<>(BiblatexEntryTypeDefinitions.ALL); defaultTypes.addAll(BiblatexSoftwareEntryTypeDefinitions.ALL); + defaultTypes.addAll(BiblatexAPAEntryTypeDefinitions.ALL); assertEquals(defaultTypes, entryTypesManager.getAllTypes(BibDatabaseMode.BIBLATEX)); } diff --git a/src/test/java/org/jabref/model/entry/field/FieldFactoryTest.java b/src/test/java/org/jabref/model/entry/field/FieldFactoryTest.java index 4ee6c716a49..ddb60cb61a7 100644 --- a/src/test/java/org/jabref/model/entry/field/FieldFactoryTest.java +++ b/src/test/java/org/jabref/model/entry/field/FieldFactoryTest.java @@ -1,8 +1,11 @@ package org.jabref.model.entry.field; +import org.jabref.model.entry.types.BiblatexApaEntryType; + import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; class FieldFactoryTest { @Test @@ -14,4 +17,14 @@ void testOrFieldsTwoTerms() { void testOrFieldsThreeTerms() { assertEquals("aaa/bbb/ccc", FieldFactory.serializeOrFields(new UnknownField("aaa"), new UnknownField("bbb"), new UnknownField("ccc"))); } + + @Test + void testDoesNotParseApaFieldWithoutEntryType() { + assertNotEquals(BiblatexApaField.ARTICLE, FieldFactory.parseField("article")); + } + + @Test + void testDoesParseApaFieldWithEntryType() { + assertEquals(BiblatexApaField.ARTICLE, FieldFactory.parseField(BiblatexApaEntryType.Constitution, "article")); + } } diff --git a/src/test/resources/org/jabref/logic/exporter/OldOpenOfficeCalcExportFormatContentSingleEntry.xml b/src/test/resources/org/jabref/logic/exporter/OldOpenOfficeCalcExportFormatContentSingleEntry.xml index 9695f3bdea2..0594bb222be 100644 --- a/src/test/resources/org/jabref/logic/exporter/OldOpenOfficeCalcExportFormatContentSingleEntry.xml +++ b/src/test/resources/org/jabref/logic/exporter/OldOpenOfficeCalcExportFormatContentSingleEntry.xml @@ -150,12 +150,6 @@ Gender - - Hal_id - - - Hal_version - Holder @@ -171,9 +165,6 @@ Introduction - - Introducedin - ISBN @@ -216,9 +207,6 @@ Library - - License - Location @@ -291,18 +279,9 @@ Related - - Relatedtype - - - Relatedstring - Reportno - - Repository - Review @@ -333,9 +312,6 @@ Subtitle - - Swhid - Title @@ -566,15 +542,6 @@ - - - - - - - - - 0097-8418 @@ -638,9 +605,6 @@ - - - 4 @@ -719,18 +683,6 @@ - - - - - - - - - - - - Design and usability in security systems: daily life as a context of use? From 0a1e98b7a6006eb7b6d38cac6663dcef21b8d2a1 Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Mon, 1 Aug 2022 21:27:48 +0200 Subject: [PATCH 17/31] Update BST VM to Antlr4 (#8934) * migrate to antlr4 * apply ide suggestions * Refactor * Introduce BstVM, wip * read, entry, sort wip * functions visitor * bstEntryContext * function, identifier, stackitem, exception * reorder * execute, iterate, reverse * removed old vm, prepared test architecture * fixed parser * fixed stringscommand * fixed macrocommand * fixed functioncommand * added exception handling for functions * testVisitEntryCommand * testVisitReadCommand * reorder and cleanup * testdata cleanup * fix function call * fixed call.type * wip if * wip if * addedTests * finally fixed if * wip tests * stylistic issues * Reworded * Improved error logging * Fix error msg * wip * Fixed tests * Fixed tests * Removed TestVM * Added reverse test * Added visitStackitem test * Added bbl tests * clean up Co-authored-by: Oliver Kopp --- build.gradle | 14 +- src/main/antlr3/org/jabref/bst/Bst.g | 96 -- src/main/antlr4/org/jabref/bst/Bst.g4 | 85 ++ src/main/java/module-info.java | 1 - .../org/jabref/logic/bst/BibtexWidth.java | 241 ---- .../java/org/jabref/logic/bst/BstEntry.java | 21 + .../org/jabref/logic/bst/BstFunctions.java | 931 ++++++++++++ .../jabref/logic/bst/BstPreviewLayout.java | 6 +- src/main/java/org/jabref/logic/bst/BstVM.java | 114 ++ .../org/jabref/logic/bst/BstVMContext.java | 22 + .../org/jabref/logic/bst/BstVMException.java | 7 + .../org/jabref/logic/bst/BstVMVisitor.java | 263 ++++ .../jabref/logic/bst/ChangeCaseFunction.java | 65 - .../jabref/logic/bst/FormatNameFunction.java | 67 - .../org/jabref/logic/bst/PurifyFunction.java | 43 - .../jabref/logic/bst/TextPrefixFunction.java | 54 - src/main/java/org/jabref/logic/bst/VM.java | 1245 ----------------- .../org/jabref/logic/bst/VMException.java | 8 - src/main/java/org/jabref/logic/bst/Warn.java | 7 - .../org/jabref/logic/bst/WidthFunction.java | 43 - .../BstCaseChanger.java} | 79 +- .../BstNameFormatter.java} | 89 +- .../BstPurifier.java} | 27 +- .../BstTextPrefixer.java} | 21 +- .../logic/bst/util/BstWidthCalculator.java | 241 ++++ .../logic/layout/format/NameFormatter.java | 6 +- .../jabref/logic/bst/BstFunctionsTest.java | 666 +++++++++ .../java/org/jabref/logic/bst/BstVMTest.java | 220 +++ .../jabref/logic/bst/BstVMVisitorTest.java | 249 ++++ .../java/org/jabref/logic/bst/TestVM.java | 634 --------- .../BstCaseChangersTest.java} | 14 +- .../BstNameFormatterTest.java} | 68 +- .../BstPurifierTest.java} | 7 +- .../BstTextPrefixerTest.java} | 7 +- .../BstWidthCalculatorTest.java} | 8 +- 35 files changed, 2971 insertions(+), 2698 deletions(-) delete mode 100644 src/main/antlr3/org/jabref/bst/Bst.g create mode 100644 src/main/antlr4/org/jabref/bst/Bst.g4 delete mode 100644 src/main/java/org/jabref/logic/bst/BibtexWidth.java create mode 100644 src/main/java/org/jabref/logic/bst/BstEntry.java create mode 100644 src/main/java/org/jabref/logic/bst/BstFunctions.java create mode 100644 src/main/java/org/jabref/logic/bst/BstVM.java create mode 100644 src/main/java/org/jabref/logic/bst/BstVMContext.java create mode 100644 src/main/java/org/jabref/logic/bst/BstVMException.java create mode 100644 src/main/java/org/jabref/logic/bst/BstVMVisitor.java delete mode 100644 src/main/java/org/jabref/logic/bst/ChangeCaseFunction.java delete mode 100644 src/main/java/org/jabref/logic/bst/FormatNameFunction.java delete mode 100644 src/main/java/org/jabref/logic/bst/PurifyFunction.java delete mode 100644 src/main/java/org/jabref/logic/bst/TextPrefixFunction.java delete mode 100644 src/main/java/org/jabref/logic/bst/VM.java delete mode 100644 src/main/java/org/jabref/logic/bst/VMException.java delete mode 100644 src/main/java/org/jabref/logic/bst/Warn.java delete mode 100644 src/main/java/org/jabref/logic/bst/WidthFunction.java rename src/main/java/org/jabref/logic/bst/{BibtexCaseChanger.java => util/BstCaseChanger.java} (83%) rename src/main/java/org/jabref/logic/bst/{BibtexNameFormatter.java => util/BstNameFormatter.java} (76%) rename src/main/java/org/jabref/logic/bst/{BibtexPurify.java => util/BstPurifier.java} (77%) rename src/main/java/org/jabref/logic/bst/{BibtexTextPrefix.java => util/BstTextPrefixer.java} (84%) create mode 100644 src/main/java/org/jabref/logic/bst/util/BstWidthCalculator.java create mode 100644 src/test/java/org/jabref/logic/bst/BstFunctionsTest.java create mode 100644 src/test/java/org/jabref/logic/bst/BstVMTest.java create mode 100644 src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java delete mode 100644 src/test/java/org/jabref/logic/bst/TestVM.java rename src/test/java/org/jabref/logic/bst/{BibtexCaseChangersTest.java => util/BstCaseChangersTest.java} (93%) rename src/test/java/org/jabref/logic/bst/{BibtexNameFormatterTest.java => util/BstNameFormatterTest.java} (52%) rename src/test/java/org/jabref/logic/bst/{BibtexPurifyTest.java => util/BstPurifierTest.java} (80%) rename src/test/java/org/jabref/logic/bst/{TextPrefixFunctionTest.java => util/BstTextPrefixerTest.java} (73%) rename src/test/java/org/jabref/logic/bst/{BibtexWidthTest.java => util/BstWidthCalculatorTest.java} (91%) diff --git a/build.gradle b/build.gradle index 3c75afdba28..0fc50f10ed7 100644 --- a/build.gradle +++ b/build.gradle @@ -85,7 +85,6 @@ repositories { } configurations { - antlr3 antlr4 // TODO: Remove the following workaround for split error messages such as // error: module java.xml.bind reads package javax.annotation from both jsr305 and java.annotation @@ -139,9 +138,6 @@ dependencies { implementation 'io.github.java-diff-utils:java-diff-utils:4.12' implementation 'info.debatty:java-string-similarity:2.0.0' - antlr3 'org.antlr:antlr:3.5.3' - implementation 'org.antlr:antlr-runtime:3.5.3' - antlr4 'org.antlr:antlr4:4.9.3' implementation 'org.antlr:antlr4-runtime:4.9.3' @@ -273,14 +269,14 @@ task generateSource(dependsOn: ["generateBstGrammarSource", } tasks.register("generateBstGrammarSource", JavaExec) { - main = "org.antlr.Tool" - classpath = configurations.antlr3 + main = "org.antlr.v4.Tool" + classpath = configurations.antlr4 group = "JabRef" - description = 'Generates BstLexer.java and BstParser.java from the Bst.g grammar file using antlr3.' + description = 'Generates BstLexer.java and BstParser.java from the Bst.g grammar file using antlr4.' - inputs.dir('src/main/antlr3/org/jabref/bst/') + inputs.dir('src/main/antlr4/org/jabref/bst/') outputs.dir("src-gen/main/java/org/jabref/logic/bst/") - args = ["-o", "src-gen/main/java/org/jabref/logic/bst/" , "$projectDir/src/main/antlr3/org/jabref/bst/Bst.g" ] + args = ["-o", "src-gen/main/java/org/jabref/logic/bst/", "-visitor", "-no-listener", "-package", "org.jabref.logic.bst", "$projectDir/src/main/antlr4/org/jabref/bst/Bst.g4"] } tasks.register("generateSearchGrammarSource", JavaExec) { diff --git a/src/main/antlr3/org/jabref/bst/Bst.g b/src/main/antlr3/org/jabref/bst/Bst.g deleted file mode 100644 index 498621ad1a1..00000000000 --- a/src/main/antlr3/org/jabref/bst/Bst.g +++ /dev/null @@ -1,96 +0,0 @@ -grammar Bst; - -options { - output=AST; -} - -tokens { - IDLIST; - STACK; - ENTRY; - COMMANDS; -} - -// applies only to the parser: -@header {// Generated by ANTLR -package org.jabref.logic.bst;} - -// applies only to the lexer: -@lexer::header {// Generated by ANTLR -package org.jabref.logic.bst;} - -program : commands+ -> ^(COMMANDS commands+); - -commands - : STRINGS^ idList - | INTEGERS^ idList - | FUNCTION^ id stack - | MACRO^ id '{'! STRING '}'! - | READ^ - | EXECUTE^ '{'! function '}'! - | ITERATE^ '{'! function '}'! - | REVERSE^ '{'! function '}'! - | ENTRY^ idList0 idList0 idList0 - | SORT^; - -identifier - : IDENTIFIER; - -id - : '{'! identifier '}'!; - -idList - : '{' identifier+ '}' -> ^(IDLIST identifier+); - -idList0 - : '{' identifier* '}' -> ^(IDLIST identifier*); - -function - : '<' | '>' | '=' | '+' | '-' | ':=' | '*' | identifier; - -stack - : '{' stackitem+ '}' -> ^(STACK stackitem+); - -stackitem - : function - | STRING - | INTEGER - | QUOTED - | stack; - -STRINGS : 'STRINGS'; -INTEGERS : 'INTEGERS'; -FUNCTION : 'FUNCTION'; -EXECUTE : 'EXECUTE'; -SORT : 'SORT'; -ITERATE : 'ITERATE'; -REVERSE : 'REVERSE'; -ENTRY : 'ENTRY'; -READ : 'READ'; -MACRO : 'MACRO'; - -QUOTED - : '\'' IDENTIFIER; - -IDENTIFIER - : LETTER (LETTER|NUMERAL|'_')* ; - -fragment LETTER - : ('a'..'z'|'A'..'Z'|'.'|'$'); - -STRING - : '"' (~('"'))* '"'; - -INTEGER - : '#' ('+'|'-')? NUMERAL+ ; - -fragment NUMERAL - : ('0'..'9'); - -WS - : (' '|'\t'|'\n'|'\r')+ {_channel=99;} ; - -LINE_COMMENT - : '%' ~('\n'|'\r')* '\r'? '\n' {_channel=99;} - ; - diff --git a/src/main/antlr4/org/jabref/bst/Bst.g4 b/src/main/antlr4/org/jabref/bst/Bst.g4 new file mode 100644 index 00000000000..92b96ce18df --- /dev/null +++ b/src/main/antlr4/org/jabref/bst/Bst.g4 @@ -0,0 +1,85 @@ +grammar Bst; + +// Lexer + +STRINGS : 'STRINGS'; +INTEGERS : 'INTEGERS'; +FUNCTION : 'FUNCTION'; +EXECUTE : 'EXECUTE'; +SORT : 'SORT'; +ITERATE : 'ITERATE'; +REVERSE : 'REVERSE'; +ENTRY : 'ENTRY'; +READ : 'READ'; +MACRO : 'MACRO'; + +GT : '>'; +LT : '<'; +EQUAL : '='; +ASSIGN : ':='; +ADD : '+'; +SUB : '-'; +CONCAT : '*'; +LBRACE : '{'; +RBRACE : '}'; + +fragment LETTER : ('a'..'z'|'A'..'Z'|'.'|'$'); +fragment NUMERAL : ('0'..'9'); + +IDENTIFIER : LETTER (LETTER|NUMERAL|'_')*; +INTEGER : '#' ('+'|'-')? NUMERAL+; +QUOTED : '\'' IDENTIFIER; +STRING : '"' (~('"'))* '"'; + +WS: [ \r\n\t]+ -> skip; +LINE_COMMENT : '%' ~('\n'|'\r')* '\r'? '\n' -> skip; + +// Parser + +bstFile + : commands+ EOF + ; + +commands + : STRINGS ids=idListObl #stringsCommand + | INTEGERS ids=idListObl #integersCommand + | FUNCTION LBRACE id=identifier RBRACE function=stack #functionCommand + | MACRO LBRACE id=identifier RBRACE LBRACE repl=STRING RBRACE #macroCommand + | READ #readCommand + | EXECUTE LBRACE bstFunction RBRACE #executeCommand + | ITERATE LBRACE bstFunction RBRACE #iterateCommand + | REVERSE LBRACE bstFunction RBRACE #reverseCommand + | ENTRY idListOpt idListOpt idListOpt #entryCommand + | SORT #sortCommand + ; + +identifier + : IDENTIFIER + ; + +// Obligatory identifier list +idListObl + : LBRACE identifier+ RBRACE + ; + +// Optional identifier list +idListOpt + : LBRACE identifier* RBRACE + ; + +bstFunction + : LT | GT | EQUAL | ADD | SUB | ASSIGN | CONCAT + | identifier + ; + +stack + : LBRACE stackitem+ RBRACE + ; + +stackitem + : bstFunction + | STRING + | INTEGER + | QUOTED + | stack + ; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 751863df3e1..c05e10bda2b 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -85,7 +85,6 @@ requires org.mariadb.jdbc; uses org.mariadb.jdbc.credential.CredentialPlugin; requires org.apache.commons.lang3; - requires antlr.runtime; requires org.antlr.antlr4.runtime; requires org.fxmisc.flowless; requires org.apache.tika.core; diff --git a/src/main/java/org/jabref/logic/bst/BibtexWidth.java b/src/main/java/org/jabref/logic/bst/BibtexWidth.java deleted file mode 100644 index 61e0b099f20..00000000000 --- a/src/main/java/org/jabref/logic/bst/BibtexWidth.java +++ /dev/null @@ -1,241 +0,0 @@ -package org.jabref.logic.bst; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * The |built_in| function {\.{purify\$}} pops the top (string) literal, removes - * nonalphanumeric characters except for |white_space| and |sep_char| characters - * (these get converted to a |space|) and removes certain alphabetic characters - * contained in the control sequences associated with a special character, and - * pushes the resulting string. If the literal isn't a string, it complains and - * pushes the null string. - * - */ -public class BibtexWidth { - - private static final Logger LOGGER = LoggerFactory.getLogger(BibtexWidth.class); - - /* - * Quoted from Bibtex: - * - * Now we initialize the system-dependent |char_width| array, for which - * |space| is the only |white_space| character given a nonzero printing - * width. The widths here are taken from Stanford's June~'87 $cmr10$~font - * and represent hundredths of a point (rounded), but since they're used - * only for relative comparisons, the units have no meaning. - */ - - private static int[] widths; - - static { - if (BibtexWidth.widths == null) { - BibtexWidth.widths = new int[128]; - - for (int i = 0; i < 128; i++) { - BibtexWidth.widths[i] = 0; - } - BibtexWidth.widths[32] = 278; - BibtexWidth.widths[33] = 278; - BibtexWidth.widths[34] = 500; - BibtexWidth.widths[35] = 833; - BibtexWidth.widths[36] = 500; - BibtexWidth.widths[37] = 833; - BibtexWidth.widths[38] = 778; - BibtexWidth.widths[39] = 278; - BibtexWidth.widths[40] = 389; - BibtexWidth.widths[41] = 389; - BibtexWidth.widths[42] = 500; - BibtexWidth.widths[43] = 778; - BibtexWidth.widths[44] = 278; - BibtexWidth.widths[45] = 333; - BibtexWidth.widths[46] = 278; - BibtexWidth.widths[47] = 500; - BibtexWidth.widths[48] = 500; - BibtexWidth.widths[49] = 500; - BibtexWidth.widths[50] = 500; - BibtexWidth.widths[51] = 500; - BibtexWidth.widths[52] = 500; - BibtexWidth.widths[53] = 500; - BibtexWidth.widths[54] = 500; - BibtexWidth.widths[55] = 500; - BibtexWidth.widths[56] = 500; - BibtexWidth.widths[57] = 500; - BibtexWidth.widths[58] = 278; - BibtexWidth.widths[59] = 278; - BibtexWidth.widths[60] = 278; - BibtexWidth.widths[61] = 778; - BibtexWidth.widths[62] = 472; - BibtexWidth.widths[63] = 472; - BibtexWidth.widths[64] = 778; - BibtexWidth.widths[65] = 750; - BibtexWidth.widths[66] = 708; - BibtexWidth.widths[67] = 722; - BibtexWidth.widths[68] = 764; - BibtexWidth.widths[69] = 681; - BibtexWidth.widths[70] = 653; - BibtexWidth.widths[71] = 785; - BibtexWidth.widths[72] = 750; - BibtexWidth.widths[73] = 361; - BibtexWidth.widths[74] = 514; - BibtexWidth.widths[75] = 778; - BibtexWidth.widths[76] = 625; - BibtexWidth.widths[77] = 917; - BibtexWidth.widths[78] = 750; - BibtexWidth.widths[79] = 778; - BibtexWidth.widths[80] = 681; - BibtexWidth.widths[81] = 778; - BibtexWidth.widths[82] = 736; - BibtexWidth.widths[83] = 556; - BibtexWidth.widths[84] = 722; - BibtexWidth.widths[85] = 750; - BibtexWidth.widths[86] = 750; - BibtexWidth.widths[87] = 1028; - BibtexWidth.widths[88] = 750; - BibtexWidth.widths[89] = 750; - BibtexWidth.widths[90] = 611; - BibtexWidth.widths[91] = 278; - BibtexWidth.widths[92] = 500; - BibtexWidth.widths[93] = 278; - BibtexWidth.widths[94] = 500; - BibtexWidth.widths[95] = 278; - BibtexWidth.widths[96] = 278; - BibtexWidth.widths[97] = 500; - BibtexWidth.widths[98] = 556; - BibtexWidth.widths[99] = 444; - BibtexWidth.widths[100] = 556; - BibtexWidth.widths[101] = 444; - BibtexWidth.widths[102] = 306; - BibtexWidth.widths[103] = 500; - BibtexWidth.widths[104] = 556; - BibtexWidth.widths[105] = 278; - BibtexWidth.widths[106] = 306; - BibtexWidth.widths[107] = 528; - BibtexWidth.widths[108] = 278; - BibtexWidth.widths[109] = 833; - BibtexWidth.widths[110] = 556; - BibtexWidth.widths[111] = 500; - BibtexWidth.widths[112] = 556; - BibtexWidth.widths[113] = 528; - BibtexWidth.widths[114] = 392; - BibtexWidth.widths[115] = 394; - BibtexWidth.widths[116] = 389; - BibtexWidth.widths[117] = 556; - BibtexWidth.widths[118] = 528; - BibtexWidth.widths[119] = 722; - BibtexWidth.widths[120] = 528; - BibtexWidth.widths[121] = 528; - BibtexWidth.widths[122] = 444; - BibtexWidth.widths[123] = 500; - BibtexWidth.widths[124] = 1000; - BibtexWidth.widths[125] = 500; - BibtexWidth.widths[126] = 500; - } - } - - private BibtexWidth() { - } - - private static int getSpecialCharWidth(char[] c, int pos) { - if ((pos + 1) < c.length) { - if ((c[pos] == 'o') && (c[pos + 1] == 'e')) { - return 778; - } - if ((c[pos] == 'O') && (c[pos + 1] == 'E')) { - return 1014; - } - if ((c[pos] == 'a') && (c[pos + 1] == 'e')) { - return 722; - } - if ((c[pos] == 'A') && (c[pos + 1] == 'E')) { - return 903; - } - if ((c[pos] == 's') && (c[pos + 1] == 's')) { - return 500; - } - } - return BibtexWidth.getCharWidth(c[pos]); - } - - public static int getCharWidth(char c) { - if ((c >= 0) && (c < 128)) { - return BibtexWidth.widths[c]; - } else { - return 0; - } - } - - public static int width(String toMeasure) { - /* - * From Bibtex: We use the natural width for all but special characters, - * and we complain if the string isn't brace-balanced. - */ - - int i = 0; - int n = toMeasure.length(); - int braceLevel = 0; - char[] c = toMeasure.toCharArray(); - int result = 0; - - /* - * From Bibtex: - * - * We use the natural widths of all characters except that some - * characters have no width: braces, control sequences (except for the - * usual 13 accented and foreign characters, whose widths are given in - * the next module), and |white_space| following control sequences (even - * a null control sequence). - * - */ - while (i < n) { - if (c[i] == '{') { - braceLevel++; - if ((braceLevel == 1) && ((i + 1) < n) && (c[i + 1] == '\\')) { - i++; // skip brace - while ((i < n) && (braceLevel > 0)) { - i++; // skip backslash - - int afterBackslash = i; - while ((i < n) && Character.isLetter(c[i])) { - i++; - } - if ((i < n) && (i == afterBackslash)) { - i++; // Skip non-alpha control seq - } else { - if (BibtexCaseChanger.findSpecialChar(c, afterBackslash).isPresent()) { - result += BibtexWidth.getSpecialCharWidth(c, afterBackslash); - } - } - while ((i < n) && Character.isWhitespace(c[i])) { - i++; - } - while ((i < n) && (braceLevel > 0) && (c[i] != '\\')) { - if (c[i] == '}') { - braceLevel--; - } else if (c[i] == '{') { - braceLevel++; - } else { - result += BibtexWidth.getCharWidth(c[i]); - } - i++; - } - } - continue; - } - } else if (c[i] == '}') { - if (braceLevel > 0) { - braceLevel--; - } else { - LOGGER.warn("Too many closing braces in string: " + toMeasure); - } - } - result += BibtexWidth.getCharWidth(c[i]); - i++; - } - if (braceLevel > 0) { - LOGGER.warn("No enough closing braces in string: " + toMeasure); - } - return result; - } -} diff --git a/src/main/java/org/jabref/logic/bst/BstEntry.java b/src/main/java/org/jabref/logic/bst/BstEntry.java new file mode 100644 index 00000000000..89571de8046 --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstEntry.java @@ -0,0 +1,21 @@ +package org.jabref.logic.bst; + +import java.util.HashMap; +import java.util.Map; + +import org.jabref.model.entry.BibEntry; + +public class BstEntry { + + public final BibEntry entry; + + public final Map localStrings = new HashMap<>(); + + public final Map fields = new HashMap<>(); + + public final Map localIntegers = new HashMap<>(); + + public BstEntry(BibEntry e) { + this.entry = e; + } +} diff --git a/src/main/java/org/jabref/logic/bst/BstFunctions.java b/src/main/java/org/jabref/logic/bst/BstFunctions.java new file mode 100644 index 00000000000..6689f83b2a5 --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstFunctions.java @@ -0,0 +1,931 @@ +package org.jabref.logic.bst; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jabref.logic.bst.util.BstCaseChanger; +import org.jabref.logic.bst.util.BstNameFormatter; +import org.jabref.logic.bst.util.BstPurifier; +import org.jabref.logic.bst.util.BstTextPrefixer; +import org.jabref.logic.bst.util.BstWidthCalculator; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.Author; +import org.jabref.model.entry.AuthorList; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BstFunctions { + private static final Logger LOGGER = LoggerFactory.getLogger(BstFunctions.class); + private static final Pattern ADD_PERIOD_PATTERN = Pattern.compile("([^.?!}\\s])(}|\\s)*$"); + + private final Map strings; + private final Map integers; + private final Map functions; + private final String preamble; + + private final Stack stack; + private final StringBuilder bbl; + + private int bstWarning = 0; + + @FunctionalInterface + public interface BstFunction { + + void execute(BstVMVisitor visitor, ParserRuleContext ctx); + + default void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntryContext) { + this.execute(visitor, ctx); + } + } + + public BstFunctions(BstVMContext bstVMContext, + StringBuilder bbl) { + this.strings = bstVMContext.strings(); + this.integers = bstVMContext.integers(); + this.functions = bstVMContext.functions(); + this.preamble = Optional.ofNullable(bstVMContext.bibDatabase()).flatMap(BibDatabase::getPreamble).orElse(""); + this.stack = bstVMContext.stack(); + + this.bbl = bbl; + } + + protected Map getBuiltInFunctions() { + Map builtInFunctions = new HashMap<>(); + + builtInFunctions.put(">", this::bstIsGreaterThan); + builtInFunctions.put("<", this::bstIsLowerThan); + builtInFunctions.put("=", this::bstEquals); + builtInFunctions.put("+", this::bstAdd); + builtInFunctions.put("-", this::bstSubtract); + builtInFunctions.put("*", this::bstConcat); + builtInFunctions.put(":=", new BstAssignFunction()); + builtInFunctions.put("add.period$", this::bstAddPeriod); + builtInFunctions.put("call.type$", new BstCallTypeFunction()); + builtInFunctions.put("change.case$", this::bstChangeCase); + builtInFunctions.put("chr.to.int$", this::bstChrToInt); + builtInFunctions.put("cite$", new BstCiteFunction()); + builtInFunctions.put("duplicate$", this::bstDuplicate); + builtInFunctions.put("empty$", this::bstEmpty); + builtInFunctions.put("format.name$", this::bstFormatName); + builtInFunctions.put("if$", this::bstIf); + builtInFunctions.put("int.to.chr$", this::bstIntToChr); + builtInFunctions.put("int.to.str$", this::bstIntToStr); + builtInFunctions.put("missing$", this::bstMissing); + builtInFunctions.put("newline$", this::bstNewLine); + builtInFunctions.put("num.names$", this::bstNumNames); + builtInFunctions.put("pop$", this::bstPop); + builtInFunctions.put("preamble$", this::bstPreamble); + builtInFunctions.put("purify$", this::bstPurify); + builtInFunctions.put("quote$", this::bstQuote); + builtInFunctions.put("skip$", this::bstSkip); + builtInFunctions.put("stack$", this::bstStack); + builtInFunctions.put("substring$", this::bstSubstring); + builtInFunctions.put("swap$", this::bstSwap); + builtInFunctions.put("text.length$", this::bstTextLength); + builtInFunctions.put("text.prefix$", this::bstTextPrefix); + builtInFunctions.put("top$", this::bstTop); + builtInFunctions.put("type$", new BstTypeFunction()); + builtInFunctions.put("warning$", this::bstWarning); + builtInFunctions.put("while$", this::bstWhile); + builtInFunctions.put("width$", this::bstWidth); + builtInFunctions.put("write$", this::bstWrite); + + return builtInFunctions; + } + + /** + * Pops the top two (integer) literals, compares them, and pushes + * the integer 1 if the second is greater than the first, 0 + * otherwise. + */ + private void bstIsGreaterThan(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation > (line %d)".formatted(ctx.start.getLine())); + } + Object o2 = stack.pop(); + Object o1 = stack.pop(); + + if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { + throw new BstVMException("Can only compare two integers with >"); + } + + stack.push(((Integer) o1).compareTo((Integer) o2) > 0 ? BstVM.TRUE : BstVM.FALSE); + } + + /** + * Pops the top two (integer) literals, compares them, and pushes + * the integer 1 if the second is lower than the first, 0 + * otherwise. + */ + private void bstIsLowerThan(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation <"); + } + Object o2 = stack.pop(); + Object o1 = stack.pop(); + + if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { + throw new BstVMException("Can only compare two integers with < (line %d)".formatted(ctx.start.getLine())); + } + + stack.push(((Integer) o1).compareTo((Integer) o2) < 0 ? BstVM.TRUE : BstVM.FALSE); + } + + /** + * Pops the top two (both integer or both string) literals, compares + * them, and pushes the integer 1 if they're equal, 0 otherwise. + */ + private void bstEquals(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation = (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + Object o2 = stack.pop(); + + if ((o1 == null) ^ (o2 == null)) { + stack.push(BstVM.FALSE); + return; + } + + if ((o1 == null) && (o2 == null)) { + stack.push(BstVM.TRUE); + return; + } + + stack.push(o1.equals(o2) ? BstVM.TRUE : BstVM.FALSE); + } + + /** + * Pops the top two (integer) literals and pushes their sum. + */ + private void bstAdd(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation + (line %d)".formatted(ctx.start.getLine())); + } + Object o2 = stack.pop(); + Object o1 = stack.pop(); + + if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { + throw new BstVMException("Can only compare two integers with + (line %d)".formatted(ctx.start.getLine())); + } + + stack.push((Integer) o1 + (Integer) o2); + } + + /** + * Pops the top two (integer) literals and pushes their difference + * (the first subtracted from the second). + */ + private void bstSubtract(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation - (line %d)".formatted(ctx.start.getLine())); + } + Object o2 = stack.pop(); + Object o1 = stack.pop(); + + if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { + throw new BstVMException("Can only subtract two integers with - (line %d)".formatted(ctx.start.getLine())); + } + + stack.push((Integer) o1 - (Integer) o2); + } + + /** + * Pops the top two (string) literals, concatenates them (in reverse + * order, that is, the order in which pushed), and pushes the + * resulting string. + */ + private void bstConcat(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation * (line %d)".formatted(ctx.start.getLine())); + } + Object o2 = stack.pop(); + Object o1 = stack.pop(); + + if (o1 == null) { + o1 = ""; + } + if (o2 == null) { + o2 = ""; + } + + if (!((o1 instanceof String) && (o2 instanceof String))) { + LOGGER.error("o1: {} ({})", o1, o1.getClass()); + LOGGER.error("o2: {} ({})", o2, o2.getClass()); + throw new BstVMException("Can only concatenate two String with * (line %d)".formatted(ctx.start.getLine())); + } + + stack.push(o1.toString() + o2); + } + + /** + * Pops the top two literals and assigns to the first (which must be + * a global or entry variable) the value of the second. + */ + public class BstAssignFunction implements BstFunction { + + @Override + public void execute(BstVMVisitor visitor, ParserRuleContext ctx) { + this.execute(visitor, ctx, null); + } + + @Override + public void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntry) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation := (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + Object o2 = stack.pop(); + + if (!(o1 instanceof BstVMVisitor.Identifier identifier)) { + throw new BstVMException("Invalid parameters (line %d)".formatted(ctx.start.getLine())); + } + String name = identifier.name(); + + if (o2 instanceof String value) { + if ((bstEntry != null) && bstEntry.localStrings.containsKey(name)) { + bstEntry.localStrings.put(name, value); + return; + } + + if (strings.containsKey(name)) { + strings.put(name, value); + } + } else if (o2 instanceof Integer value) { + if ((bstEntry != null) && bstEntry.localIntegers.containsKey(name)) { + bstEntry.localIntegers.put(name, value); + return; + } + + if (integers.containsKey(name)) { + integers.put(name, value); + } + } else { + throw new BstVMException("Invalid parameters (line %d)".formatted(ctx.start.getLine())); + } + } + } + + /** + * Pops the top (string) literal, adds a `.' to it if the last non + * '}' character isn't a `.', `?', or `!', and pushes this resulting + * string. + */ + private void bstAddPeriod(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation add.period$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String s)) { + throw new BstVMException("Can only add a period to a string for add.period$ (line %d)".formatted(ctx.start.getLine())); + } + + Matcher m = ADD_PERIOD_PATTERN.matcher(s); + + if (m.find()) { + StringBuilder sb = new StringBuilder(); + m.appendReplacement(sb, m.group(1)); + sb.append('.'); + String group2 = m.group(2); + if (group2 != null) { + sb.append(m.group(2)); + } + stack.push(sb.toString()); + } else { + stack.push(s); + } + } + + /** + * Executes the function whose name is the entry type of an entry. + * For example if an entry is of type book, this function executes + * the book function. When given as an argument to the ITERATE + * command, call.type$ actually produces the output for the entries. + * For an entry with an unknown type, it executes the function + * default.type. Thus you should define (before the READ command) + * one function for each standard entry type as well as a + * default.type function. + */ + public class BstCallTypeFunction implements BstFunction { + @Override + public void execute(BstVMVisitor visitor, ParserRuleContext ctx) { + throw new BstVMException("Call.type$ can only be called from within a context (ITERATE or REVERSE). (line %d)".formatted(ctx.start.getLine())); + } + + @Override + public void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntry) { + if (bstEntry == null) { + this.execute(visitor, ctx); // Throw error + } else { + functions.get(bstEntry.entry.getType().getName()).execute(visitor, ctx, bstEntry); + } + } + } + + /** + * Pops the top two (string) literals; it changes the case of the second + * according to the specifications of the first, as follows. (Note: The word + * `letters' in the next sentence refers only to those at brace-level 0, the + * top-most brace level; no other characters are changed, except perhaps for + * \special characters", described in Section 4.) If the first literal is the + * string `t', it converts to lower case all letters except the very first + * character in the string, which it leaves alone, and except the first + * character following any colon and then nonnull white space, which it also + * leaves alone; if it's the string `l', it converts all letters to lower case; + * and if it's the string `u', it converts all letters to upper case. It then + * pushes this resulting string. If either type is incorrect, it complains and + * pushes the null string; however, if both types are correct but the + * specification string (i.e., the first string) isn't one of the legal ones, it + * merely pushes the second back onto the stack, after complaining. (Another + * note: It ignores case differences in the specification string; for example, + * the strings t and T are equivalent for the purposes of this built-in + * function.) + */ + private void bstChangeCase(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation change.case$ (line %d)".formatted(ctx.start.getLine())); + } + + Object o1 = stack.pop(); + if (!((o1 instanceof String format) && (format.length() == 1))) { + throw new BstVMException("A format string of length 1 is needed for change.case$ (line %d)".formatted(ctx.start.getLine())); + } + + Object o2 = stack.pop(); + if (!(o2 instanceof String toChange)) { + throw new BstVMException("A string is needed as second parameter for change.case$ (line %d)".formatted(ctx.start.getLine())); + } + + stack.push(BstCaseChanger.changeCase(toChange, BstCaseChanger.FormatMode.of(format))); + } + + /** + * Pops the top (string) literal, makes sure it's a single + * character, converts it to the corresponding ASCII integer, and + * pushes this integer. + */ + private void bstChrToInt(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation chr.to.int$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + if (!((o1 instanceof String s) && (((String) o1).length() == 1))) { + throw new BstVMException("Can only perform chr.to.int$ on string with length 1 (line %d)".formatted(ctx.start.getLine())); + } + + stack.push((int) s.charAt(0)); + } + + /** + * Pushes the string that was the \cite-command argument for this + * entry. + */ + public class BstCiteFunction implements BstFunction { + @Override + public void execute(BstVMVisitor visitor, ParserRuleContext ctx) { + throw new BstVMException("Must have an entry to cite$ (line %d)".formatted(ctx.start.getLine())); + } + + @Override + public void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntryContext) { + if (bstEntryContext == null) { + execute(visitor, ctx); + return; + } + + stack.push(bstEntryContext.entry.getCitationKey().orElse(null)); + } + } + + /** + * Pops the top literal from the stack and pushes two copies of it. + */ + private void bstDuplicate(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation duplicate$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + stack.push(o1); + stack.push(o1); + } + + /** + * Pops the top literal and pushes the integer 1 if it's a missing + * field or a string having no non-white-space characters, 0 + * otherwise. + */ + private void bstEmpty(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation empty$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + if (o1 == null) { + stack.push(BstVM.TRUE); + return; + } + + if (!(o1 instanceof String s)) { + throw new BstVMException("Operand does not match function empty$ (line %d)".formatted(ctx.start.getLine())); + } + + stack.push("".equals(s.trim()) ? BstVM.TRUE : BstVM.FALSE); + } + + /** + * The |built_in| function {\.{format.name\$}} pops the + * top three literals (they are a string, an integer, and a string + * literal, in that order). The last string literal represents a + * name list (each name corresponding to a person), the integer + * literal specifies which name to pick from this list, and the + * first string literal specifies how to format this name, as + * described in the \BibTeX\ documentation. Finally, this function + * pushes the formatted name. If any of the types is incorrect, it + * complains and pushes the null string. + */ + private void bstFormatName(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 3) { + throw new BstVMException("Not enough operands on stack for operation format.name$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + Object o2 = stack.pop(); + Object o3 = stack.pop(); + + if (!(o1 instanceof String) && !(o2 instanceof Integer) && !(o3 instanceof String)) { + // warning("A string is needed for change.case$"); + stack.push(""); + return; + } + + String format = (String) o1; + Integer name = (Integer) o2; + String names = (String) o3; + + if (names == null) { + stack.push(""); + } else { + AuthorList a = AuthorList.parse(names); + if (name > a.getNumberOfAuthors()) { + throw new BstVMException("Author Out of Bounds. Number %d invalid for %s (line %d)".formatted(name, names, ctx.start.getLine())); + } + Author author = a.getAuthor(name - 1); + + stack.push(BstNameFormatter.formatName(author, format)); + } + } + + /** + * Pops the top three literals (they are two function literals and + * an integer literal, in that order); if the integer is greater + * than 0, it executes the second literal, else it executes the + * first. + */ + private void bstIf(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 3) { + throw new BstVMException("Not enough operands on stack for if$ (line %d)".formatted(ctx.start.getLine())); + } + + Object f1 = stack.pop(); + Object f2 = stack.pop(); + Object i = stack.pop(); + + if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof ParseTree)) + && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof ParseTree)) + && (i instanceof Integer)) { + throw new BstVMException("Expecting two functions and an integer for if$ (line %d)".formatted(ctx.start.getLine())); + } + + if (((Integer) i) > 0) { + callIdentifierOrTree(f2, visitor, ctx); + } else { + callIdentifierOrTree(f1, visitor, ctx); + } + } + + private void callIdentifierOrTree(Object f, BstVMVisitor visitor, ParserRuleContext ctx) { + if (f instanceof ParseTree tree) { + visitor.visit(tree); + } else if (f instanceof BstVMVisitor.Identifier identifier) { + visitor.resolveIdentifier(identifier.name(), ctx); + } else { + stack.push(f); + } + } + + /** + * Pops the top (integer) literal, interpreted as the ASCII integer + * value of a single character, converts it to the corresponding + * single-character string, and pushes this string. + */ + private void bstIntToChr(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation int.to.chr$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof Integer i)) { + throw new BstVMException("Can only perform operation int.to.chr$ on an Integer (line %d)".formatted(ctx.start.getLine())); + } + + stack.push(String.valueOf((char) i.intValue())); + } + + /** + * Pops the top (integer) literal, converts it to its (unique) + * string equivalent, and pushes this string. + */ + private void bstIntToStr(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation int.to.str$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof Integer)) { + throw new BstVMException("Can only transform an integer to an string using int.to.str$ (line %d)".formatted(ctx.start.getLine())); + } + + stack.push(o1.toString()); + } + + /** + * Pops the top literal and pushes the integer 1 if it's a missing + * field, 0 otherwise. + */ + private void bstMissing(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation missing$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + if (o1 == null) { + stack.push(BstVM.TRUE); + return; + } + + if (!(o1 instanceof String)) { + LOGGER.warn("Not a string or missing field in operation missing$ (line %d)".formatted(ctx.start.getLine())); + stack.push(BstVM.TRUE); + return; + } + + stack.push(BstVM.FALSE); + } + + /** + * Writes onto the bbl file what is accumulated in the output buffer. + * It writes a blank line if and only if the output buffer is empty. + * Since write$ does reasonable line breaking, you should use this + * function only when you want a blank line or an explicit line + * break. + */ + private void bstNewLine(BstVMVisitor visitor, ParserRuleContext ctx) { + this.bbl.append('\n'); + } + + /** + * Pops the top (string) literal and pushes the number of names the + * string represents one plus the number of occurrences of the + * substring "and" (ignoring case differences) surrounded by + * non-null white-space at the top brace level. + */ + private void bstNumNames(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation num.names$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String s)) { + throw new BstVMException("Need a string at the top of the stack for num.names$ (line %d)".formatted(ctx.start.getLine())); + } + + stack.push(AuthorList.parse(s).getNumberOfAuthors()); + } + + /** + * Pops the top of the stack but doesn't print it; this gets rid of + * an unwanted stack literal. + */ + private void bstPop(BstVMVisitor visitor, ParserRuleContext ctx) { + stack.pop(); + } + + /** + * The |built_in| function {\.{preamble\$}} pushes onto the stack + * the concatenation of all the \.{preamble} strings read from the + * database files. (or the empty string if there were none) + * '@PREAMBLE' strings are read from the database files. + */ + private void bstPreamble(BstVMVisitor visitor, ParserRuleContext ctx) { + stack.push(preamble); + } + + /** + * Pops the top (string) literal, removes nonalphanumeric characters + * except for white-space characters and hyphens and ties (these all get + * converted to a space), removes certain alphabetic characters + * contained in the control sequences associated with a \special + * character", and pushes the resulting string. + */ + private void bstPurify(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation purify$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String)) { + LOGGER.warn("A string is needed for purify$"); + stack.push(""); + return; + } + + stack.push(BstPurifier.purify((String) o1)); + } + + /** + * Pushes the string consisting of the double-quote character. + */ + private void bstQuote(BstVMVisitor visitor, ParserRuleContext ctx) { + stack.push("\""); + } + + /** + * Does nothing. + */ + private void bstSkip(BstVMVisitor visitor, ParserRuleContext ctx) { + // no-op + } + + /** + * Pops and prints the whole stack; it's meant to be used for style + * designers while debugging. + */ + private void bstStack(BstVMVisitor visitor, ParserRuleContext ctx) { + while (!stack.empty()) { + LOGGER.debug("Stack entry {}", stack.pop()); + } + } + + /** + * Pops the top three literals (they are the two integers literals + * len and start, and a string literal, in that order). It pushes + * the substring of the (at most) len consecutive characters + * starting at the startth character (assuming 1-based indexing) if + * start is positive, and ending at the start-th character + * (including) from the end if start is negative (where the first + * character from the end is the last character). + */ + private void bstSubstring(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 3) { + throw new BstVMException("Not enough operands on stack for operation substring$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + Object o2 = stack.pop(); + Object o3 = stack.pop(); + + if (!((o1 instanceof Integer len) && (o2 instanceof Integer start) && (o3 instanceof String s))) { + throw new BstVMException("Expecting two integers and a string for substring$ (line %d)".formatted(ctx.start.getLine())); + } + + int lenI = len; + int startI = start; + + if (lenI > (Integer.MAX_VALUE / 2)) { + lenI = Integer.MAX_VALUE / 2; + } + + if (startI > (Integer.MAX_VALUE / 2)) { + startI = Integer.MAX_VALUE / 2; + } + + if (startI < (Integer.MIN_VALUE / 2)) { + startI = -Integer.MIN_VALUE / 2; + } + + if (startI < 0) { + startI += s.length() + 1; + startI = Math.max(1, (startI + 1) - lenI); + } + stack.push(s.substring(startI - 1, Math.min((startI - 1) + lenI, s.length()))); + } + + /** + * Swaps the top two literals on the stack. text.length$ Pops the + * top (string) literal, and pushes the number of text characters + * it contains, where an accented character (more precisely, a + * \special character", defined in Section 4) counts as a single + * text character, even if it's missing its matching right brace, + * and where braces don't count as text characters. + */ + private void bstSwap(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation swap$ (line %d)".formatted(ctx.start.getLine())); + } + Object f1 = stack.pop(); + Object f2 = stack.pop(); + + stack.push(f1); + stack.push(f2); + } + + /** + * text.length$ Pops the top (string) literal, and pushes the number + * of text characters it contains, where an accented character (more + * precisely, a "special character", defined in Section 4) counts as + * a single text character, even if it's missing its matching right + * brace, and where braces don't count as text characters. + * + * From BibTeXing: For the purposes of counting letters in labels, + * BibTEX considers everything contained inside the braces as a + * single letter. + */ + private void bstTextLength(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation text.length$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String s)) { + throw new BstVMException("Can only perform operation on a string text.length$ (line %d)".formatted(ctx.start.getLine())); + } + + char[] c = s.toCharArray(); + int result = 0; + int i = 0; + int n = s.length(); + int braceLevel = 0; + + while (i < n) { + i++; + if (c[i - 1] == '{') { + braceLevel++; + if ((braceLevel == 1) && (i < n)) { + if (c[i] == '\\') { + i++; // skip over backslash + while ((i < n) && (braceLevel > 0)) { + if (c[i] == '}') { + braceLevel--; + } else if (c[i] == '{') { + braceLevel++; + } + i++; + } + result++; + } + } + } else if (c[i - 1] == '}') { + if (braceLevel > 0) { + braceLevel--; + } + } else { + result++; + } + } + stack.push(result); + } + + /** + * Pops the top two literals (the integer literal len and a string + * literal, in that order). It pushes the substring of the (at most) len + * consecutive text characters starting from the beginning of the + * string. This function is similar to substring$, but this one + * considers a \special character", even if it's missing its matching + * right brace, to be a single text character (rather than however many + * ASCII characters it actually comprises), and this function doesn't + * consider braces to be text characters; furthermore, this function + * appends any needed matching right braces. + */ + private void bstTextPrefix(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation text.prefix$ (line %d)".formatted(ctx.start.getLine())); + } + + Object o1 = stack.pop(); + if (!(o1 instanceof Integer)) { + LOGGER.warn("An integer is needed as first parameter to text.prefix$ (line {})", ctx.start.getLine()); + stack.push(""); + return; + } + + Object o2 = stack.pop(); + if (!(o2 instanceof String)) { + LOGGER.warn("A string is needed as second parameter to text.prefix$ (line {})", ctx.start.getLine()); + stack.push(""); + return; + } + + stack.push(BstTextPrefixer.textPrefix((Integer) o1, (String) o2)); + } + + /** + * Pops and prints the top of the stack to the log file. It's useful for debugging. + */ + private void bstTop(BstVMVisitor visitor, ParserRuleContext ctx) { + LOGGER.debug("Stack entry {} (line {})", stack.pop(), ctx.start.getLine()); + } + + /** + * Pushes the current entry's type (book, article, etc.), but pushes + * the null string if the type is either unknown or undefined. + */ + public class BstTypeFunction implements BstFunction { + @Override + public void execute(BstVMVisitor visitor, ParserRuleContext ctx) { + throw new BstVMException("type$ need a context (line %d)".formatted(ctx.start.getLine())); + } + + @Override + public void execute(BstVMVisitor visitor, ParserRuleContext ctx, BstEntry bstEntryContext) { + if (bstEntryContext == null) { + this.execute(visitor, ctx); + return; + } + + stack.push(bstEntryContext.entry.getType().getName()); + } + } + + /** + * Pops the top (string) literal and prints it following a warning + * message. This also increments a count of the number of warning + * messages issued. + */ + private void bstWarning(BstVMVisitor visitor, ParserRuleContext ctx) { + LOGGER.warn("Warning (#{}): {}", bstWarning++, stack.pop()); + } + + /** + * Pops the top two (function) literals, and keeps executing the + * second as long as the (integer) literal left on the stack by + * executing the first is greater than 0. + */ + private void bstWhile(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.size() < 2) { + throw new BstVMException("Not enough operands on stack for operation while$ (line %d)".formatted(ctx.start.getLine())); + } + Object f2 = stack.pop(); + Object f1 = stack.pop(); + + if (!((f1 instanceof BstVMVisitor.Identifier) || (f1 instanceof ParseTree)) + && ((f2 instanceof BstVMVisitor.Identifier) || (f2 instanceof ParseTree))) { + throw new BstVMException("Expecting two functions for while$ (line %d)".formatted(ctx.start.getLine())); + } + + do { + visitor.visit((ParseTree) f1); + + Object i = stack.pop(); + if (!(i instanceof Integer)) { + throw new BstVMException("First parameter to while has to return an integer but was %s (line %d)" + .formatted(i.toString(), ctx.start.getLine())); + } + if ((Integer) i <= 0) { + break; + } + visitor.visit((ParseTree) f2); + } while (true); + } + + /** + * The |built_in| function {\.{width\$}} pops the top (string) literal and + * pushes the integer that represents its width in units specified by the + * |char_width| array. This function takes the literal literally; that is, it + * assumes each character in the string is to be printed as is, regardless of + * whether the character has a special meaning to \TeX, except that special + * characters (even without their |right_brace|s) are handled specially. If the + * literal isn't a string, it complains and pushes~0. + */ + private void bstWidth(BstVMVisitor visitor, ParserRuleContext ctx) { + if (stack.isEmpty()) { + throw new BstVMException("Not enough operands on stack for operation width$ (line %d)".formatted(ctx.start.getLine())); + } + Object o1 = stack.pop(); + + if (!(o1 instanceof String)) { + LOGGER.warn("A string is needed for width$"); + stack.push(0); + return; + } + + stack.push(BstWidthCalculator.width((String) o1)); + } + + /** + * Pops the top (string) literal and writes it on the output buffer + * (which will result in stuff being written onto the bbl file when + * the buffer fills up). + */ + private void bstWrite(BstVMVisitor visitor, ParserRuleContext ctx) { + String s = (String) stack.pop(); + bbl.append(s); + } +} diff --git a/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java b/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java index d0c040842fd..a002d388e60 100644 --- a/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java +++ b/src/main/java/org/jabref/logic/bst/BstPreviewLayout.java @@ -23,7 +23,7 @@ public class BstPreviewLayout implements PreviewLayout { private final String name; - private VM vm; + private BstVM bstVM; private String error; public BstPreviewLayout(Path path) { @@ -34,7 +34,7 @@ public BstPreviewLayout(Path path) { return; } try { - vm = new VM(path.toFile()); + bstVM = new BstVM(path); } catch (Exception e) { LOGGER.error("Could not read {}.", path.toAbsolutePath(), e); error = Localization.lang("Error opening file '%0'.", path.toString()); @@ -49,7 +49,7 @@ public String generatePreview(BibEntry originalEntry, BibDatabaseContext databas // ensure that the entry is of BibTeX format (and do not modify the original entry) BibEntry entry = (BibEntry) originalEntry.clone(); new ConvertToBibtexCleanup().cleanup(entry); - String result = vm.run(List.of(entry)); + String result = bstVM.render(List.of(entry)); // Remove all comments result = result.replaceAll("%.*", ""); // Remove all LaTeX comments diff --git a/src/main/java/org/jabref/logic/bst/BstVM.java b/src/main/java/org/jabref/logic/bst/BstVM.java new file mode 100644 index 00000000000..a2428d683d9 --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstVM.java @@ -0,0 +1,114 @@ +package org.jabref.logic.bst; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Stack; + +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; + +import org.antlr.v4.runtime.BailErrorStrategy; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.antlr.v4.runtime.tree.ParseTree; + +public class BstVM { + + protected static final Integer FALSE = 0; + protected static final Integer TRUE = 1; + + protected final ParseTree tree; + protected BstVMContext latestContext; // for testing + + private Path path = null; + + public BstVM(Path path) throws RecognitionException, IOException { + this(CharStreams.fromPath(path)); + this.path = path; + } + + public BstVM(String s) throws RecognitionException { + this(CharStreams.fromString(s)); + } + + protected BstVM(CharStream bst) throws RecognitionException { + this(charStream2CommonTree(bst)); + } + + private BstVM(ParseTree tree) { + this.tree = tree; + } + + private static ParseTree charStream2CommonTree(CharStream query) { + BstLexer lexer = new BstLexer(query); + lexer.removeErrorListeners(); + lexer.addErrorListener(ThrowingErrorListener.INSTANCE); + BstParser parser = new BstParser(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); + parser.addErrorListener(ThrowingErrorListener.INSTANCE); + parser.setErrorHandler(new BailErrorStrategy()); + return parser.bstFile(); + } + + /** + * Transforms the given list of BibEntries to a rendered list of references using the parsed bst program + * + * @param bibEntries list of entries to convert + * @param bibDatabase (may be null) the bibDatabase used for resolving strings / crossref + * @return list of references in plain text form + */ + public String render(Collection bibEntries, BibDatabase bibDatabase) { + Objects.requireNonNull(bibEntries); + + List entries = new ArrayList<>(bibEntries.size()); + for (BibEntry entry : bibEntries) { + entries.add(new BstEntry(entry)); + } + + StringBuilder resultBuffer = new StringBuilder(); + + BstVMContext bstVMContext = new BstVMContext(entries, bibDatabase, path); + bstVMContext.functions().putAll(new BstFunctions(bstVMContext, resultBuffer).getBuiltInFunctions()); + bstVMContext.integers().put("entry.max$", Integer.MAX_VALUE); + bstVMContext.integers().put("global.max$", Integer.MAX_VALUE); + + BstVMVisitor bstVMVisitor = new BstVMVisitor(bstVMContext, resultBuffer); + bstVMVisitor.visit(tree); + + latestContext = bstVMContext; + + return resultBuffer.toString(); + } + + public String render(Collection bibEntries) { + return render(bibEntries, null); + } + + protected Stack getStack() { + if (latestContext != null) { + return latestContext.stack(); + } else { + throw new BstVMException("BstVM must have rendered at least once to provide the latest stack"); + } + } + + private static class ThrowingErrorListener extends BaseErrorListener { + public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener(); + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, + int line, int charPositionInLine, String msg, RecognitionException e) + throws ParseCancellationException { + throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg); + } + } +} diff --git a/src/main/java/org/jabref/logic/bst/BstVMContext.java b/src/main/java/org/jabref/logic/bst/BstVMContext.java new file mode 100644 index 00000000000..8a98f605dd8 --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstVMContext.java @@ -0,0 +1,22 @@ +package org.jabref.logic.bst; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Stack; + +import org.jabref.model.database.BibDatabase; + +public record BstVMContext(List entries, + Map strings, + Map integers, + Map functions, + Stack stack, + BibDatabase bibDatabase, + Optional path) { + public BstVMContext(List entries, BibDatabase bibDatabase, Path path) { + this(entries, new HashMap<>(), new HashMap<>(), new HashMap<>(), new Stack<>(), bibDatabase, Optional.ofNullable(path)); + } +} diff --git a/src/main/java/org/jabref/logic/bst/BstVMException.java b/src/main/java/org/jabref/logic/bst/BstVMException.java new file mode 100644 index 00000000000..2851166d094 --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstVMException.java @@ -0,0 +1,7 @@ +package org.jabref.logic.bst; + +public class BstVMException extends RuntimeException { + public BstVMException(String string) { + super(string); + } +} diff --git a/src/main/java/org/jabref/logic/bst/BstVMVisitor.java b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java new file mode 100644 index 00000000000..a815480e506 --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/BstVMVisitor.java @@ -0,0 +1,263 @@ +package org.jabref.logic.bst; + +import java.util.Comparator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.jabref.logic.bibtex.FieldContentFormatterPreferences; +import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.bibtex.FieldWriterPreferences; +import org.jabref.logic.bibtex.InvalidFieldValueException; +import org.jabref.model.entry.Month; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.FieldFactory; +import org.jabref.model.entry.field.StandardField; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class BstVMVisitor extends BstBaseVisitor { + private static final Logger LOGGER = LoggerFactory.getLogger(BstVMVisitor.class); + + private final BstVMContext bstVMContext; + private final StringBuilder bbl; + + private BstEntry selectedBstEntry = null; + + public record Identifier(String name) { + } + + public BstVMVisitor(BstVMContext bstVMContext, StringBuilder bbl) { + this.bstVMContext = bstVMContext; + this.bbl = bbl; + } + + @Override + public Integer visitStringsCommand(BstParser.StringsCommandContext ctx) { + if (ctx.ids.identifier().size() > 20) { + throw new BstVMException("Strings limit reached"); + } + + for (BstParser.IdentifierContext identifierContext : ctx.ids.identifier()) { + bstVMContext.strings().put(identifierContext.getText(), null); + } + return BstVM.TRUE; + } + + @Override + public Integer visitIntegersCommand(BstParser.IntegersCommandContext ctx) { + for (BstParser.IdentifierContext identifierContext : ctx.ids.identifier()) { + bstVMContext.integers().put(identifierContext.getText(), 0); + } + return BstVM.TRUE; + } + + @Override + public Integer visitFunctionCommand(BstParser.FunctionCommandContext ctx) { + bstVMContext.functions().put(ctx.id.getText(), + (visitor, functionContext) -> visitor.visit(ctx.function)); + return BstVM.TRUE; + } + + @Override + public Integer visitMacroCommand(BstParser.MacroCommandContext ctx) { + String replacement = ctx.repl.getText().substring(1, ctx.repl.getText().length() - 1); + bstVMContext.functions().put(ctx.id.getText(), + (visitor, functionContext) -> bstVMContext.stack().push(replacement)); + return BstVM.TRUE; + } + + @Override + public Integer visitReadCommand(BstParser.ReadCommandContext ctx) { + FieldWriter fieldWriter = new FieldWriter(new FieldWriterPreferences(true, List.of(StandardField.MONTH), new FieldContentFormatterPreferences())); + for (BstEntry e : bstVMContext.entries()) { + for (Map.Entry mEntry : e.fields.entrySet()) { + Field field = FieldFactory.parseField(mEntry.getKey()); + String fieldValue = e.entry.getResolvedFieldOrAlias(field, bstVMContext.bibDatabase()) + .map(content -> { + try { + String result = fieldWriter.write(field, content); + if (result.startsWith("{")) { + // Strip enclosing {} from the output + return result.substring(1, result.length() - 1); + } + if (field == StandardField.MONTH) { + // We don't have the internal BibTeX strings at hand. + // Thus, we look up the full month name in the generic table. + return Month.parse(result) + .map(Month::getFullName) + .orElse(result); + } + return result; + } catch ( + InvalidFieldValueException invalidFieldValueException) { + // in case there is something wrong with the content, just return the content itself + return content; + } + }) + .orElse(null); + mEntry.setValue(fieldValue); + } + } + + for (BstEntry e : bstVMContext.entries()) { + if (!e.fields.containsKey(StandardField.CROSSREF.getName())) { + e.fields.put(StandardField.CROSSREF.getName(), null); + } + } + + return BstVM.TRUE; + } + + @Override + public Integer visitExecuteCommand(BstParser.ExecuteCommandContext ctx) { + this.selectedBstEntry = null; + visit(ctx.bstFunction()); + + return BstVM.TRUE; + } + + @Override + public Integer visitIterateCommand(BstParser.IterateCommandContext ctx) { + for (BstEntry entry : bstVMContext.entries()) { + this.selectedBstEntry = entry; + visit(ctx.bstFunction()); + } + + return BstVM.TRUE; + } + + @Override + public Integer visitReverseCommand(BstParser.ReverseCommandContext ctx) { + ListIterator i = bstVMContext.entries().listIterator(bstVMContext.entries().size()); + while (i.hasPrevious()) { + this.selectedBstEntry = i.previous(); + visit(ctx.bstFunction()); + } + + return BstVM.TRUE; + } + + @Override + public Integer visitEntryCommand(BstParser.EntryCommandContext ctx) { + // ENTRY command contains 3 optionally filled identifier lists: + // Fields, Integers and Strings + + BstParser.IdListOptContext entryFields = ctx.idListOpt(0); + for (BstParser.IdentifierContext identifierContext : entryFields.identifier()) { + for (BstEntry entry : bstVMContext.entries()) { + entry.fields.put(identifierContext.getText(), null); + } + } + + BstParser.IdListOptContext entryIntegers = ctx.idListOpt(1); + for (BstParser.IdentifierContext identifierContext : entryIntegers.identifier()) { + for (BstEntry entry : bstVMContext.entries()) { + entry.localIntegers.put(identifierContext.getText(), 0); + } + } + + BstParser.IdListOptContext entryStrings = ctx.idListOpt(2); + for (BstParser.IdentifierContext identifierContext : entryStrings.identifier()) { + for (BstEntry entry : bstVMContext.entries()) { + entry.localStrings.put(identifierContext.getText(), null); + } + } + + for (BstEntry entry : bstVMContext.entries()) { + entry.localStrings.put("sort.key$", null); + } + + return BstVM.TRUE; + } + + @Override + public Integer visitSortCommand(BstParser.SortCommandContext ctx) { + bstVMContext.entries().sort(Comparator.comparing(o -> (o.localStrings.get("sort.key$")))); + return BstVM.TRUE; + } + + @Override + public Integer visitIdentifier(BstParser.IdentifierContext ctx) { + resolveIdentifier(ctx.IDENTIFIER().getText(), ctx); + return BstVM.TRUE; + } + + protected void resolveIdentifier(String name, ParserRuleContext ctx) { + if (selectedBstEntry != null) { + if (selectedBstEntry.fields.containsKey(name)) { + bstVMContext.stack().push(selectedBstEntry.fields.get(name)); + return; + } + if (selectedBstEntry.localStrings.containsKey(name)) { + bstVMContext.stack().push(selectedBstEntry.localStrings.get(name)); + return; + } + if (selectedBstEntry.localIntegers.containsKey(name)) { + bstVMContext.stack().push(selectedBstEntry.localIntegers.get(name)); + return; + } + } + + if (bstVMContext.strings().containsKey(name)) { + bstVMContext.stack().push(bstVMContext.strings().get(name)); + return; + } + if (bstVMContext.integers().containsKey(name)) { + bstVMContext.stack().push(bstVMContext.integers().get(name)); + return; + } + if (bstVMContext.functions().containsKey(name)) { + bstVMContext.functions().get(name).execute(this, ctx); + return; + } + + throw new BstVMException("No matching identifier found: " + name); + } + + @Override + public Integer visitBstFunction(BstParser.BstFunctionContext ctx) { + String name = ctx.getChild(0).getText(); + if (bstVMContext.functions().containsKey(name)) { + bstVMContext.functions().get(name).execute(this, ctx, selectedBstEntry); + } else { + visit(ctx.getChild(0)); + } + + return BstVM.TRUE; + } + + @Override + public Integer visitStackitem(BstParser.StackitemContext ctx) { + for (ParseTree childNode : ctx.children) { + try { + if (childNode instanceof TerminalNode token) { + switch (token.getSymbol().getType()) { + case BstParser.STRING -> { + String s = token.getText(); + bstVMContext.stack().push(s.substring(1, s.length() - 1)); + } + case BstParser.INTEGER -> + bstVMContext.stack().push(Integer.parseInt(token.getText().substring(1))); + case BstParser.QUOTED -> + bstVMContext.stack().push(new Identifier(token.getText().substring(1))); + } + } else if (childNode instanceof BstParser.StackContext) { + bstVMContext.stack().push(childNode); + } else { + this.visit(childNode); + } + } catch (BstVMException e) { + bstVMContext.path().ifPresentOrElse( + (path) -> LOGGER.error("{} ({})", e.getMessage(), path), + () -> LOGGER.error(e.getMessage())); + throw e; + } + } + return BstVM.TRUE; + } +} diff --git a/src/main/java/org/jabref/logic/bst/ChangeCaseFunction.java b/src/main/java/org/jabref/logic/bst/ChangeCaseFunction.java deleted file mode 100644 index 65f4f64c619..00000000000 --- a/src/main/java/org/jabref/logic/bst/ChangeCaseFunction.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.jabref.logic.bst; - -import java.util.Locale; -import java.util.Stack; - -import org.jabref.logic.bst.BibtexCaseChanger.FORMAT_MODE; -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.BstFunction; - -/** - * From the Bibtex manual: - * - * Pops the top two (string) literals; it changes the case of the second - * according to the specifications of the first, as follows. (Note: The word - * `letters' in the next sentence refers only to those at brace-level 0, the - * top-most brace level; no other characters are changed, except perhaps for - * \special characters", described in Section 4.) If the first literal is the - * string `t', it converts to lower case all letters except the very first - * character in the string, which it leaves alone, and except the first - * character following any colon and then nonnull white space, which it also - * leaves alone; if it's the string `l', it converts all letters to lower case; - * and if it's the string `u', it converts all letters to upper case. It then - * pushes this resulting string. If either type is incorrect, it complains and - * pushes the null string; however, if both types are correct but the - * specification string (i.e., the first string) isn't one of the legal ones, it - * merely pushes the second back onto the stack, after complaining. (Another - * note: It ignores case differences in the specification string; for example, - * the strings t and T are equivalent for the purposes of this built-in - * function.) - * - * Christopher: I think this should be another grammar! This parser is horrible. - * - */ -public class ChangeCaseFunction implements BstFunction { - - private final VM vm; - - public ChangeCaseFunction(VM vm) { - this.vm = vm; - } - - @Override - public void execute(BstEntry context) { - Stack stack = vm.getStack(); - - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation change.case$"); - } - - Object o1 = stack.pop(); - if (!((o1 instanceof String) && (((String) o1).length() == 1))) { - throw new VMException("A format string of length 1 is needed for change.case$"); - } - - Object o2 = stack.pop(); - if (!(o2 instanceof String)) { - throw new VMException("A string is needed as second parameter for change.case$"); - } - - char format = ((String) o1).toLowerCase(Locale.ROOT).charAt(0); - String s = (String) o2; - - stack.push(BibtexCaseChanger.changeCase(s, FORMAT_MODE.getFormatModeForBSTFormat(format))); - } -} diff --git a/src/main/java/org/jabref/logic/bst/FormatNameFunction.java b/src/main/java/org/jabref/logic/bst/FormatNameFunction.java deleted file mode 100644 index b2845c89be7..00000000000 --- a/src/main/java/org/jabref/logic/bst/FormatNameFunction.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.jabref.logic.bst; - -import java.util.Stack; - -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.BstFunction; -import org.jabref.model.entry.Author; -import org.jabref.model.entry.AuthorList; - -/** - * From Bibtex: - * - * "The |built_in| function {\.{format.name\$}} pops the - * top three literals (they are a string, an integer, and a string - * literal, in that order). The last string literal represents a - * name list (each name corresponding to a person), the integer - * literal specifies which name to pick from this list, and the - * first string literal specifies how to format this name, as - * described in the \BibTeX\ documentation. Finally, this function - * pushes the formatted name. If any of the types is incorrect, it - * complains and pushes the null string." - * - * All the pain is encapsulated in BibtexNameFormatter. :-) - * - */ -public class FormatNameFunction implements BstFunction { - - private final VM vm; - - public FormatNameFunction(VM vm) { - this.vm = vm; - } - - @Override - public void execute(BstEntry context) { - Stack stack = vm.getStack(); - - if (stack.size() < 3) { - throw new VMException("Not enough operands on stack for operation format.name$"); - } - Object o1 = stack.pop(); - Object o2 = stack.pop(); - Object o3 = stack.pop(); - - if (!(o1 instanceof String) && !(o2 instanceof Integer) && !(o3 instanceof String)) { - // warning("A string is needed for change.case$"); - stack.push(""); - return; - } - - String format = (String) o1; - Integer name = (Integer) o2; - String names = (String) o3; - - if (names == null) { - stack.push(""); - } else { - AuthorList a = AuthorList.parse(names); - if (name > a.getNumberOfAuthors()) { - throw new VMException("Author Out of Bounds. Number " + name + " invalid for " + names); - } - Author author = a.getAuthor(name - 1); - - stack.push(BibtexNameFormatter.formatName(author, format, vm)); - } - } -} diff --git a/src/main/java/org/jabref/logic/bst/PurifyFunction.java b/src/main/java/org/jabref/logic/bst/PurifyFunction.java deleted file mode 100644 index b0df9df3735..00000000000 --- a/src/main/java/org/jabref/logic/bst/PurifyFunction.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.jabref.logic.bst; - -import java.util.Stack; - -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.BstFunction; - -/** - * - * The |built_in| function {\.{purify\$}} pops the top (string) literal, removes - * nonalphanumeric characters except for |white_space| and |sep_char| characters - * (these get converted to a |space|) and removes certain alphabetic characters - * contained in the control sequences associated with a special character, and - * pushes the resulting string. If the literal isn't a string, it complains and - * pushes the null string. - * - */ -public class PurifyFunction implements BstFunction { - - private final VM vm; - - public PurifyFunction(VM vm) { - this.vm = vm; - } - - @Override - public void execute(BstEntry context) { - Stack stack = vm.getStack(); - - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation purify$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String)) { - vm.warn("A string is needed for purify$"); - stack.push(""); - return; - } - - stack.push(BibtexPurify.purify((String) o1, vm)); - } -} diff --git a/src/main/java/org/jabref/logic/bst/TextPrefixFunction.java b/src/main/java/org/jabref/logic/bst/TextPrefixFunction.java deleted file mode 100644 index 415ee97498b..00000000000 --- a/src/main/java/org/jabref/logic/bst/TextPrefixFunction.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.jabref.logic.bst; - -import java.util.Stack; - -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.BstFunction; - -/** - * The |built_in| function {\.{text.prefix\$}} pops the top two literals - * (the integer literal |pop_lit1| and a string literal, in that order). - * It pushes the substring of the (at most) |pop_lit1| consecutive text - * characters starting from the beginning of the string. This function - * is similar to {\.{substring\$}}, but this one considers an accented - * character (or more precisely, a ``special character''$\!$, even if - * it's missing its matching |right_brace|) to be a single text character - * (rather than however many |ASCII_code| characters it actually - * comprises), and this function doesn't consider braces to be text - * characters; furthermore, this function appends any needed matching - * |right_brace|s. If any of the types is incorrect, it complains and - * pushes the null string. - */ -public class TextPrefixFunction implements BstFunction { - - private final VM vm; - - public TextPrefixFunction(VM vm) { - this.vm = vm; - } - - @Override - public void execute(BstEntry context) { - Stack stack = vm.getStack(); - - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation text.prefix$"); - } - - Object o1 = stack.pop(); - if (!(o1 instanceof Integer)) { - vm.warn("An integer is needed as first parameter to text.prefix$"); - stack.push(""); - return; - } - - Object o2 = stack.pop(); - if (!(o2 instanceof String)) { - vm.warn("A string is needed as second parameter to text.prefix$"); - stack.push(""); - return; - } - - stack.push(BibtexTextPrefix.textPrefix((Integer) o1, (String) o2, vm)); - } -} diff --git a/src/main/java/org/jabref/logic/bst/VM.java b/src/main/java/org/jabref/logic/bst/VM.java deleted file mode 100644 index caaf9078f64..00000000000 --- a/src/main/java/org/jabref/logic/bst/VM.java +++ /dev/null @@ -1,1245 +0,0 @@ -package org.jabref.logic.bst; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Objects; -import java.util.Stack; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.jabref.logic.bibtex.FieldContentFormatterPreferences; -import org.jabref.logic.bibtex.FieldWriter; -import org.jabref.logic.bibtex.FieldWriterPreferences; -import org.jabref.logic.bibtex.InvalidFieldValueException; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.entry.AuthorList; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.Month; -import org.jabref.model.entry.field.Field; -import org.jabref.model.entry.field.FieldFactory; -import org.jabref.model.entry.field.StandardField; - -import org.antlr.runtime.ANTLRFileStream; -import org.antlr.runtime.ANTLRStringStream; -import org.antlr.runtime.CharStream; -import org.antlr.runtime.CommonTokenStream; -import org.antlr.runtime.RecognitionException; -import org.antlr.runtime.tree.CommonTree; -import org.antlr.runtime.tree.Tree; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A BibTeX Virtual machine that can execute .bst files. - *

- * Documentation can be found in the original bibtex distribution: - *

- * https://www.ctan.org/pkg/bibtex - */ -public class VM implements Warn { - - public static final Integer FALSE = 0; - - public static final Integer TRUE = 1; - - private static final Pattern ADD_PERIOD_PATTERN = Pattern.compile("([^\\.\\?\\!\\}\\s])(\\}|\\s)*$"); - - private static final Logger LOGGER = LoggerFactory.getLogger(VM.class); - - private List entries; - - private Map strings = new HashMap<>(); - - private Map integers = new HashMap<>(); - - private Map functions = new HashMap<>(); - - private Stack stack = new Stack<>(); - - private final Map buildInFunctions; - - private File file; - - private final CommonTree tree; - - private StringBuilder bbl; - - private String preamble = ""; - - public static class Identifier { - - public final String name; - - public Identifier(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - public static class Variable { - - public final String name; - - public Variable(String name) { - this.name = name; - } - - public String getName() { - return name; - } - } - - @FunctionalInterface - public interface BstFunction { - void execute(BstEntry context); - } - - public VM(File f) throws RecognitionException, IOException { - this(new ANTLRFileStream(f.getPath())); - this.file = f; - } - - public VM(String s) throws RecognitionException { - this(new ANTLRStringStream(s)); - } - - private VM(CharStream bst) throws RecognitionException { - this(VM.charStream2CommonTree(bst)); - } - - private VM(CommonTree tree) { - this.tree = tree; - - this.buildInFunctions = new HashMap<>(37); - - /* - * Pops the top two (integer) literals, compares them, and pushes - * the integer 1 if the second is greater than the first, 0 - * otherwise. - */ - buildInFunctions.put(">", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation >"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); - - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new VMException("Can only compare two integers with >"); - } - - stack.push(((Integer) o1).compareTo((Integer) o2) > 0 ? VM.TRUE : VM.FALSE); - }); - - /* Analogous to >. */ - buildInFunctions.put("<", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation <"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); - - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new VMException("Can only compare two integers with <"); - } - - stack.push(((Integer) o1).compareTo((Integer) o2) < 0 ? VM.TRUE : VM.FALSE); - }); - - /* - * Pops the top two (both integer or both string) literals, compares - * them, and pushes the integer 1 if they're equal, 0 otherwise. - */ - buildInFunctions.put("=", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation ="); - } - Object o1 = stack.pop(); - Object o2 = stack.pop(); - - if ((o1 == null) ^ (o2 == null)) { - stack.push(VM.FALSE); - return; - } - - if ((o1 == null) && (o2 == null)) { - stack.push(VM.TRUE); - return; - } - - stack.push(o1.equals(o2) ? VM.TRUE : VM.FALSE); - }); - - /* Pops the top two (integer) literals and pushes their sum. */ - buildInFunctions.put("+", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation +"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); - - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new VMException("Can only compare two integers with +"); - } - - stack.push((Integer) o1 + (Integer) o2); - }); - - /* - * Pops the top two (integer) literals and pushes their difference - * (the first subtracted from the second). - */ - buildInFunctions.put("-", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation -"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); - - if (!((o1 instanceof Integer) && (o2 instanceof Integer))) { - throw new VMException("Can only subtract two integers with -"); - } - - stack.push((Integer) o1 - (Integer) o2); - }); - - /* - * Pops the top two (string) literals, concatenates them (in reverse - * order, that is, the order in which pushed), and pushes the - * resulting string. - */ - buildInFunctions.put("*", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation *"); - } - Object o2 = stack.pop(); - Object o1 = stack.pop(); - - if (o1 == null) { - o1 = ""; - } - if (o2 == null) { - o2 = ""; - } - - if (!((o1 instanceof String) && (o2 instanceof String))) { - LOGGER.error("o1: {} ({})", o1, o1.getClass()); - LOGGER.error("o2: {} ({})", o2, o2.getClass()); - throw new VMException("Can only concatenate two String with *"); - } - - stack.push(o1.toString() + o2); - }); - - /* - * Pops the top two literals and assigns to the first (which must be - * a global or entry variable) the value of the second. - */ - buildInFunctions.put(":=", context -> { - if (stack.size() < 2) { - throw new VMException("Invalid call to operation :="); - } - Object o1 = stack.pop(); - Object o2 = stack.pop(); - assign(context, o1, o2); - }); - - /* - * Pops the top (string) literal, adds a `.' to it if the last non - * '}' character isn't a `.', `?', or `!', and pushes this resulting - * string. - */ - buildInFunctions.put("add.period$", context -> addPeriodFunction()); - - /* - * Executes the function whose name is the entry type of an entry. - * For example if an entry is of type book, this function executes - * the book function. When given as an argument to the ITERATE - * command, call.type$ actually produces the output for the entries. - * For an entry with an unknown type, it executes the function - * default.type. Thus you should define (before the READ command) - * one function for each standard entry type as well as a - * default.type function. - */ - buildInFunctions.put("call.type$", context -> { - if (context == null) { - throw new VMException("Call.type$ can only be called from within a context (ITERATE or REVERSE)."); - } - VM.this.execute(context.entry.getType().getName(), context); - }); - - buildInFunctions.put("change.case$", new ChangeCaseFunction(this)); - - /* - * Pops the top (string) literal, makes sure it's a single - * character, converts it to the corresponding ASCII integer, and - * pushes this integer. - */ - buildInFunctions.put("chr.to.int$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation chr.to.int$"); - } - Object o1 = stack.pop(); - - if (!((o1 instanceof String) && (((String) o1).length() == 1))) { - throw new VMException("Can only perform chr.to.int$ on string with length 1"); - } - - String s = (String) o1; - - stack.push((int) s.charAt(0)); - }); - - /* - * Pushes the string that was the \cite-command argument for this - * entry. - */ - buildInFunctions.put("cite$", context -> { - if (context == null) { - throw new VMException("Must have an entry to cite$"); - } - stack.push(context.entry.getCitationKey().orElse(null)); - }); - - /* - * Pops the top literal from the stack and pushes two copies of it. - */ - buildInFunctions.put("duplicate$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation duplicate$"); - } - Object o1 = stack.pop(); - - stack.push(o1); - stack.push(o1); - }); - - /* - * Pops the top literal and pushes the integer 1 if it's a missing - * field or a string having no non-white-space characters, 0 - * otherwise. - */ - buildInFunctions.put("empty$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation empty$"); - } - Object o1 = stack.pop(); - - if (o1 == null) { - stack.push(VM.TRUE); - return; - } - - if (!(o1 instanceof String)) { - throw new VMException("Operand does not match function empty$"); - } - - String s = (String) o1; - - stack.push("".equals(s.trim()) ? VM.TRUE : VM.FALSE); - }); - - buildInFunctions.put("format.name$", new FormatNameFunction(this)); - - /* - * Pops the top three literals (they are two function literals and - * an integer literal, in that order); if the integer is greater - * than 0, it executes the second literal, else it executes the - * first. - */ - buildInFunctions.put("if$", context -> { - if (stack.size() < 3) { - throw new VMException("Not enough operands on stack for operation ="); - } - Object f1 = stack.pop(); - Object f2 = stack.pop(); - Object i = stack.pop(); - - if (!((f1 instanceof Identifier) || (f1 instanceof Tree)) - && ((f2 instanceof Identifier) || (f2 instanceof Tree)) && (i instanceof Integer)) { - throw new VMException("Expecting two functions and an integer for if$."); - } - - if ((Integer) i > 0) { - VM.this.executeInContext(f2, context); - } else { - VM.this.executeInContext(f1, context); - } - }); - - /* - * Pops the top (integer) literal, interpreted as the ASCII integer - * value of a single character, converts it to the corresponding - * single-character string, and pushes this string. - */ - buildInFunctions.put("int.to.chr$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation int.to.chr$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof Integer)) { - throw new VMException("Can only perform operation int.to.chr$ on an Integer"); - } - - Integer i = (Integer) o1; - - stack.push(String.valueOf((char) i.intValue())); - }); - - /* - * Pops the top (integer) literal, converts it to its (unique) - * string equivalent, and pushes this string. - */ - buildInFunctions.put("int.to.str$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation int.to.str$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof Integer)) { - throw new VMException("Can only transform an integer to an string using int.to.str$"); - } - - stack.push(o1.toString()); - }); - - /* - * Pops the top literal and pushes the integer 1 if it's a missing - * field, 0 otherwise. - */ - buildInFunctions.put("missing$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation missing$"); - } - Object o1 = stack.pop(); - - if (o1 == null) { - stack.push(VM.TRUE); - return; - } - - if (!(o1 instanceof String)) { - warn("Not a string or missing field in operation missing$"); - stack.push(VM.TRUE); - return; - } - - stack.push(VM.FALSE); - }); - - /* - * Writes onto the bbl file what is accumulated in the output buffer. - * It writes a blank line if and only if the output buffer is empty. - * Since write$ does reasonable line breaking, you should use this - * function only when you want a blank line or an explicit line - * break. - */ - buildInFunctions.put("newline$", context -> VM.this.bbl.append('\n')); - - /* - * Pops the top (string) literal and pushes the number of names the - * string represents one plus the number of occurrences of the - * substring "and" (ignoring case differences) surrounded by - * non-null white-space at the top brace level. - */ - buildInFunctions.put("num.names$", context -> { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation num.names$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String)) { - throw new VMException("Need a string at the top of the stack for num.names$"); - } - String s = (String) o1; - - stack.push(AuthorList.parse(s).getNumberOfAuthors()); - }); - - /* - * Pops the top of the stack but doesn't print it; this gets rid of - * an unwanted stack literal. - */ - buildInFunctions.put("pop$", context -> stack.pop()); - - /* - * The |built_in| function {\.{preamble\$}} pushes onto the stack - * the concatenation of all the \.{preamble} strings read from the - * database files. (or the empty string if there where none) - * - * @PREAMBLE strings read from the database files. - */ - buildInFunctions.put("preamble$", context -> { - stack.push(preamble); - }); - - /* - * Pops the top (string) literal, removes nonalphanumeric characters - * except for white-space characters and hyphens and ties (these all get - * converted to a space), removes certain alphabetic characters - * contained in the control sequences associated with a \special - * character", and pushes the resulting string. - */ - buildInFunctions.put("purify$", new PurifyFunction(this)); - - /* - * Pushes the string consisting of the double-quote character. - */ - buildInFunctions.put("quote$", context -> stack.push("\"")); - - /* - * Is a no-op. - */ - buildInFunctions.put("skip$", context -> { - // Nothing to do! Yeah! - }); - - /* - * Pops and prints the whole stack; it's meant to be used for style - * designers while debugging. - */ - buildInFunctions.put("stack$", context -> { - while (!stack.empty()) { - LOGGER.debug("Stack entry {}", stack.pop()); - } - }); - - /* - * Pops the top three literals (they are the two integers literals - * len and start, and a string literal, in that order). It pushes - * the substring of the (at most) len consecutive characters - * starting at the startth character (assuming 1-based indexing) if - * start is positive, and ending at the start-th character - * (including) from the end if start is negative (where the first - * character from the end is the last character). - */ - buildInFunctions.put("substring$", context -> substringFunction()); - - /* - * Swaps the top two literals on the stack. text.length$ Pops the - * top (string) literal, and pushes the number of text characters - * it contains, where an accented character (more precisely, a - * \special character", defined in Section 4) counts as a single - * text character, even if it's missing its matching right brace, - * and where braces don't count as text characters. - */ - buildInFunctions.put("swap$", context -> { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation swap$"); - } - Object f1 = stack.pop(); - Object f2 = stack.pop(); - - stack.push(f1); - stack.push(f2); - }); - - /* - * text.length$ Pops the top (string) literal, and pushes the number - * of text characters it contains, where an accented character (more - * precisely, a "special character", defined in Section 4) counts as - * a single text character, even if it's missing its matching right - * brace, and where braces don't count as text characters. - * - * From BibTeXing: For the purposes of counting letters in labels, - * BibTEX considers everything contained inside the braces as a - * single letter. - */ - buildInFunctions.put("text.length$", context -> textLengthFunction()); - - /* - * Pops the top two literals (the integer literal len and a string - * literal, in that order). It pushes the substring of the (at most) len - * consecutive text characters starting from the beginning of the - * string. This function is similar to substring$, but this one - * considers a \special character", even if it's missing its matching - * right brace, to be a single text character (rather than however many - * ASCII characters it actually comprises), and this function doesn't - * consider braces to be text characters; furthermore, this function - * appends any needed matching right braces. - */ - buildInFunctions.put("text.prefix$", new TextPrefixFunction(this)); - - /* - * Pops and prints the top of the stack to the log file. It's useful for debugging. - */ - buildInFunctions.put("top$", context -> LOGGER.debug("Stack entry {}", stack.pop())); - - /* - * Pushes the current entry's type (book, article, etc.), but pushes - * the null string if the type is either unknown or undefined. - */ - buildInFunctions.put("type$", context -> { - if (context == null) { - throw new VMException("type$ need a context."); - } - - stack.push(context.entry.getType().getName()); - }); - - /* - * Pops the top (string) literal and prints it following a warning - * message. This also increments a count of the number of warning - * messages issued. - */ - buildInFunctions.put("warning$", new BstFunction() { - int warning = 1; - - @Override - public void execute(BstEntry context) { - LOGGER.warn("Warning (#" + (warning++) + "): " + stack.pop()); - } - }); - - /* - * Pops the top two (function) literals, and keeps executing the - * second as long as the (integer) literal left on the stack by - * executing the first is greater than 0. - */ - buildInFunctions.put("while$", this::whileFunction); - - buildInFunctions.put("width$", new WidthFunction(this)); - - /* - * Pops the top (string) literal and writes it on the output buffer - * (which will result in stuff being written onto the bbl file when - * the buffer fills up). - */ - buildInFunctions.put("write$", context -> { - String s = (String) stack.pop(); - VM.this.bbl.append(s); - }); - } - - private void textLengthFunction() { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation text.length$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String)) { - throw new VMException("Can only perform operation on a string text.length$"); - } - - String s = (String) o1; - char[] c = s.toCharArray(); - int result = 0; - - // Comments from bibtex.web: - - // sp_ptr := str_start[pop_lit1]; - int i = 0; - - // sp_end := str_start[pop_lit1+1]; - int n = s.length(); - - // sp_brace_level := 0; - int braceLevel = 0; - - // while (sp_ptr < sp_end) do begin - while (i < n) { - // incr(sp_ptr); - i++; - // if (str_pool[sp_ptr-1] = left_brace) then - // begin - if (c[i - 1] == '{') { - // incr(sp_brace_level); - braceLevel++; - // if ((sp_brace_level = 1) and (sp_ptr < sp_end)) then - if ((braceLevel == 1) && (i < n)) { - // if (str_pool[sp_ptr] = backslash) then - // begin - if (c[i] == '\\') { - // incr(sp_ptr); {skip over the |backslash|} - i++; // skip over backslash - // while ((sp_ptr < sp_end) and (sp_brace_level - // > 0)) do begin - while ((i < n) && (braceLevel > 0)) { - // if (str_pool[sp_ptr] = right_brace) then - if (c[i] == '}') { - // decr(sp_brace_level) - braceLevel--; - } else if (c[i] == '{') { - // incr(sp_brace_level); - braceLevel++; - } - // incr(sp_ptr); - i++; - // end; - } - // incr(num_text_chars); - result++; - // end; - } - // end - } - - // else if (str_pool[sp_ptr-1] = right_brace) then - // begin - } else if (c[i - 1] == '}') { - // if (sp_brace_level > 0) then - if (braceLevel > 0) { - // decr(sp_brace_level); - braceLevel--; - // end - } - } else { // else - // incr(num_text_chars); - result++; - } - } - stack.push(result); - } - - private void whileFunction(BstEntry context) { - if (stack.size() < 2) { - throw new VMException("Not enough operands on stack for operation while$"); - } - Object f2 = stack.pop(); - Object f1 = stack.pop(); - - if (!((f1 instanceof Identifier) || (f1 instanceof Tree)) - && ((f2 instanceof Identifier) || (f2 instanceof Tree))) { - throw new VMException("Expecting two functions for while$."); - } - - do { - VM.this.executeInContext(f1, context); - - Object i = stack.pop(); - if (!(i instanceof Integer)) { - throw new VMException("First parameter to while has to return an integer but was " + i); - } - if ((Integer) i <= 0) { - break; - } - VM.this.executeInContext(f2, context); - } while (true); - } - - private void substringFunction() { - if (stack.size() < 3) { - throw new VMException("Not enough operands on stack for operation substring$"); - } - Object o1 = stack.pop(); - Object o2 = stack.pop(); - Object o3 = stack.pop(); - - if (!((o1 instanceof Integer) && (o2 instanceof Integer) && (o3 instanceof String))) { - throw new VMException("Expecting two integers and a string for substring$"); - } - - Integer len = (Integer) o1; - Integer start = (Integer) o2; - - int lenI = len; - int startI = start; - - if (lenI > (Integer.MAX_VALUE / 2)) { - lenI = Integer.MAX_VALUE / 2; - } - - if (startI > (Integer.MAX_VALUE / 2)) { - startI = Integer.MAX_VALUE / 2; - } - - if (startI < (Integer.MIN_VALUE / 2)) { - startI = -Integer.MIN_VALUE / 2; - } - - String s = (String) o3; - - if (startI < 0) { - startI += s.length() + 1; - startI = Math.max(1, (startI + 1) - lenI); - } - stack.push(s.substring(startI - 1, Math.min((startI - 1) + lenI, s.length()))); - } - - private void addPeriodFunction() { - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation add.period$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String)) { - throw new VMException("Can only add a period to a string for add.period$"); - } - - String s = (String) o1; - Matcher m = ADD_PERIOD_PATTERN.matcher(s); - - if (m.find()) { - StringBuilder sb = new StringBuilder(); - m.appendReplacement(sb, m.group(1)); - sb.append('.'); - String group2 = m.group(2); - if (group2 != null) { - sb.append(m.group(2)); - } - stack.push(sb.toString()); - } else { - stack.push(s); - } - } - - private static CommonTree charStream2CommonTree(CharStream bst) throws RecognitionException { - BstLexer lex = new BstLexer(bst); - CommonTokenStream tokens = new CommonTokenStream(lex); - BstParser parser = new BstParser(tokens); - BstParser.program_return r = parser.program(); - return (CommonTree) r.getTree(); - } - - private boolean assign(BstEntry context, Object o1, Object o2) { - if (!(o1 instanceof Identifier) || !((o2 instanceof String) || (o2 instanceof Integer))) { - throw new VMException("Invalid parameters"); - } - - String name = ((Identifier) o1).getName(); - - if (o2 instanceof String) { - if ((context != null) && context.localStrings.containsKey(name)) { - context.localStrings.put(name, (String) o2); - return true; - } - - if (strings.containsKey(name)) { - strings.put(name, (String) o2); - return true; - } - return false; - } - - if ((context != null) && context.localIntegers.containsKey(name)) { - context.localIntegers.put(name, (Integer) o2); - return true; - } - - if (integers.containsKey(name)) { - integers.put(name, (Integer) o2); - return true; - } - return false; - } - - public String run(BibDatabase db) { - preamble = db.getPreamble().orElse(""); - return run(db.getEntries()); - } - - public String run(Collection bibtex) { - return this.run(bibtex, null); - } - - /** - * Transforms the given list of BibEntries to a rendered list of references using the underlying bst file - * - * @param bibEntries list of entries to convert - * @param bibDatabase (may be null) the bibDatabase used for resolving strings / crossref - * @return list of references in plain text form - */ - public String run(Collection bibEntries, BibDatabase bibDatabase) { - Objects.requireNonNull(bibEntries); - - // Reset - bbl = new StringBuilder(); - - strings = new HashMap<>(); - - integers = new HashMap<>(); - integers.put("entry.max$", Integer.MAX_VALUE); - integers.put("global.max$", Integer.MAX_VALUE); - - functions = new HashMap<>(); - functions.putAll(buildInFunctions); - - stack = new Stack<>(); - - // Create entries - entries = new ArrayList<>(bibEntries.size()); - for (BibEntry entry : bibEntries) { - entries.add(new BstEntry(entry)); - } - - // Go - for (int i = 0; i < tree.getChildCount(); i++) { - Tree child = tree.getChild(i); - switch (child.getType()) { - case BstParser.STRINGS: - strings(child); - break; - case BstParser.INTEGERS: - integers(child); - break; - case BstParser.FUNCTION: - function(child); - break; - case BstParser.EXECUTE: - execute(child); - break; - case BstParser.SORT: - sort(); - break; - case BstParser.ITERATE: - iterate(child); - break; - case BstParser.REVERSE: - reverse(child); - break; - case BstParser.ENTRY: - entry(child); - break; - case BstParser.READ: - read(bibDatabase); - break; - case BstParser.MACRO: - macro(child); - break; - default: - LOGGER.info("Unknown type: {}", child.getType()); - break; - } - } - - return bbl.toString(); - } - - /** - * Dredges up from the database file the field values for each entry in the list. It has no arguments. If a database - * entry doesn't have a value for a field (and probably no database entry will have a value for every field), that - * field variable is marked as missing for the entry. - *

- * We use null for the missing entry designator. - */ - private void read(BibDatabase bibDatabase) { - FieldWriter fieldWriter = new FieldWriter(new FieldWriterPreferences(true, List.of(StandardField.MONTH), new FieldContentFormatterPreferences())); - for (BstEntry e : entries) { - for (Map.Entry mEntry : e.fields.entrySet()) { - Field field = FieldFactory.parseField(mEntry.getKey()); - String fieldValue = e.entry.getResolvedFieldOrAlias(field, bibDatabase) - .map(content -> { - try { - String result = fieldWriter.write(field, content); - if (result.startsWith("{")) { - // Strip enclosing {} from the output - return result.substring(1, result.length() - 1); - } - if (field == StandardField.MONTH) { - // We don't have the internal BibTeX strings at hand. - // We nevertheless want to have the full month name. - // Thus, we lookup the full month name here. - return Month.parse(result) - .map(month -> month.getFullName()) - .orElse(result); - } - return result; - } catch (InvalidFieldValueException invalidFieldValueException) { - // in case there is something wrong with the content, just return the content itself - return content; - } - }) - .orElse(null); - mEntry.setValue(fieldValue); - } - } - - for (BstEntry e : entries) { - if (!e.fields.containsKey(StandardField.CROSSREF.getName())) { - e.fields.put(StandardField.CROSSREF.getName(), null); - } - } - } - - /** - * Defines a string macro. It has two arguments; the first is the macro's name, which is treated like any other - * variable or function name, and the second is its definition, which must be double-quote-delimited. You must have - * one for each three-letter month abbreviation; in addition, you should have one for common journal names. The - * user's database may override any definition you define using this command. If you want to define a string the - * user can't touch, use the FUNCTION command, which has a compatible syntax. - */ - private void macro(Tree child) { - String name = child.getChild(0).getText(); - String replacement = child.getChild(1).getText(); - functions.put(name, new MacroFunction(replacement)); - } - - public class MacroFunction implements BstFunction { - - private final String replacement; - - public MacroFunction(String replacement) { - this.replacement = replacement; - } - - @Override - public void execute(BstEntry context) { - VM.this.push(replacement); - } - } - - /** - * Declares the fields and entry variables. It has three arguments, each a (possibly empty) list of variable names. - * The three lists are of: fields, integer entry variables, and string entry variables. There is an additional field - * that BibTEX automatically declares, crossref, used for cross referencing. And there is an additional string entry - * variable automatically declared, sort.key$, used by the SORT command. Each of these variables has a value for - * each entry on the list. - */ - private void entry(Tree child) { - // Fields first - Tree t = child.getChild(0); - - for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); - - for (BstEntry entry : entries) { - entry.fields.put(name, null); - } - } - - // Integers - t = child.getChild(1); - - for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); - - for (BstEntry entry : entries) { - entry.localIntegers.put(name, 0); - } - } - // Strings - t = child.getChild(2); - - for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); - for (BstEntry entry : entries) { - entry.localStrings.put(name, null); - } - } - for (BstEntry entry : entries) { - entry.localStrings.put("sort.key$", null); - } - } - - private void reverse(Tree child) { - BstFunction f = functions.get(child.getChild(0).getText()); - - ListIterator i = entries.listIterator(entries.size()); - while (i.hasPrevious()) { - f.execute(i.previous()); - } - } - - private void iterate(Tree child) { - BstFunction f = functions.get(child.getChild(0).getText()); - - for (BstEntry entry : entries) { - f.execute(entry); - } - } - - /** - * Sorts the entry list using the values of the string entry variable sort.key$. It has no arguments. - */ - private void sort() { - entries.sort(Comparator.comparing(o -> (o.localStrings.get("sort.key$")))); - } - - private void executeInContext(Object o, BstEntry context) { - if (o instanceof Tree) { - Tree t = (Tree) o; - new StackFunction(t).execute(context); - } else if (o instanceof Identifier) { - execute(((Identifier) o).getName(), context); - } - } - - private void execute(Tree child) { - execute(child.getChild(0).getText(), null); - } - - public class StackFunction implements BstFunction { - - private final Tree localTree; - - public StackFunction(Tree stack) { - localTree = stack; - } - - public Tree getTree() { - return localTree; - } - - @Override - public void execute(BstEntry context) { - for (int i = 0; i < localTree.getChildCount(); i++) { - Tree c = localTree.getChild(i); - try { - - switch (c.getType()) { - case BstParser.STRING: - String s = c.getText(); - push(s.substring(1, s.length() - 1)); - break; - case BstParser.INTEGER: - push(Integer.parseInt(c.getText().substring(1))); - break; - case BstParser.QUOTED: - push(new Identifier(c.getText().substring(1))); - break; - case BstParser.STACK: - push(c); - break; - default: - VM.this.execute(c.getText(), context); - break; - } - } catch (VMException e) { - if (file == null) { - LOGGER.error("ERROR " + e.getMessage() + " (" + c.getLine() + ")"); - } else { - LOGGER.error("ERROR " + e.getMessage() + " (" + file.getPath() + ":" - + c.getLine() + ")"); - } - throw e; - } - } - } - } - - private void push(Tree t) { - stack.push(t); - } - - private void execute(String name, BstEntry context) { - if (context != null) { - if (context.fields.containsKey(name)) { - stack.push(context.fields.get(name)); - return; - } - if (context.localStrings.containsKey(name)) { - stack.push(context.localStrings.get(name)); - return; - } - if (context.localIntegers.containsKey(name)) { - stack.push(context.localIntegers.get(name)); - return; - } - } - if (strings.containsKey(name)) { - stack.push(strings.get(name)); - return; - } - if (integers.containsKey(name)) { - stack.push(integers.get(name)); - return; - } - - if (functions.containsKey(name)) { - // OK to have a null context - functions.get(name).execute(context); - return; - } - - throw new VMException("No matching identifier found: " + name); - } - - private void function(Tree child) { - String name = child.getChild(0).getText(); - Tree localStack = child.getChild(1); - functions.put(name, new StackFunction(localStack)); - } - - /** - * Declares global integer variables. It has one argument, a list of variable names. There are two such - * automatically-declared variables, entry.max$ and global.max$, used for limiting the lengths of string vari- - * ables. You may have any number of these commands, but a variable's declaration must precede its use. - */ - private void integers(Tree child) { - Tree t = child.getChild(0); - - for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); - integers.put(name, 0); - } - } - - /** - * Declares global string variables. It has one argument, a list of variable names. You may have any number of these - * commands, but a variable's declaration must precede its use. - * - * @param child - */ - private void strings(Tree child) { - Tree t = child.getChild(0); - - for (int i = 0; i < t.getChildCount(); i++) { - String name = t.getChild(i).getText(); - strings.put(name, null); - } - } - - public static class BstEntry { - - public final BibEntry entry; - - public final Map localStrings = new HashMap<>(); - - // keys filled by org.jabref.logic.bst.VM.entry based on the contents of the bst file - public final Map fields = new HashMap<>(); - - public final Map localIntegers = new HashMap<>(); - - public BstEntry(BibEntry e) { - this.entry = e; - } - } - - private void push(Integer integer) { - stack.push(integer); - } - - private void push(String string) { - stack.push(string); - } - - private void push(Identifier identifier) { - stack.push(identifier); - } - - public Map getStrings() { - return strings; - } - - public Map getIntegers() { - return integers; - } - - public List getEntries() { - return entries; - } - - public Map getFunctions() { - return functions; - } - - public Stack getStack() { - return stack; - } - - @Override - public void warn(String string) { - LOGGER.warn(string); - } -} diff --git a/src/main/java/org/jabref/logic/bst/VMException.java b/src/main/java/org/jabref/logic/bst/VMException.java deleted file mode 100644 index f46c5cd5277..00000000000 --- a/src/main/java/org/jabref/logic/bst/VMException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.jabref.logic.bst; - -public class VMException extends RuntimeException { - - public VMException(String string) { - super(string); - } -} diff --git a/src/main/java/org/jabref/logic/bst/Warn.java b/src/main/java/org/jabref/logic/bst/Warn.java deleted file mode 100644 index 7a524ad9834..00000000000 --- a/src/main/java/org/jabref/logic/bst/Warn.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.jabref.logic.bst; - -@FunctionalInterface -public interface Warn { - - void warn(String s); -} diff --git a/src/main/java/org/jabref/logic/bst/WidthFunction.java b/src/main/java/org/jabref/logic/bst/WidthFunction.java deleted file mode 100644 index 06784267bf0..00000000000 --- a/src/main/java/org/jabref/logic/bst/WidthFunction.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.jabref.logic.bst; - -import java.util.Stack; - -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.BstFunction; - -/** - * The |built_in| function {\.{width\$}} pops the top (string) literal and - * pushes the integer that represents its width in units specified by the - * |char_width| array. This function takes the literal literally; that is, it - * assumes each character in the string is to be printed as is, regardless of - * whether the character has a special meaning to \TeX, except that special - * characters (even without their |right_brace|s) are handled specially. If the - * literal isn't a string, it complains and pushes~0. - * - */ -public class WidthFunction implements BstFunction { - - private final VM vm; - - public WidthFunction(VM vm) { - this.vm = vm; - } - - @Override - public void execute(BstEntry context) { - Stack stack = vm.getStack(); - - if (stack.isEmpty()) { - throw new VMException("Not enough operands on stack for operation width$"); - } - Object o1 = stack.pop(); - - if (!(o1 instanceof String)) { - vm.warn("A string is needed for change.case$"); - stack.push(0); - return; - } - - stack.push(BibtexWidth.width((String) o1)); - } -} diff --git a/src/main/java/org/jabref/logic/bst/BibtexCaseChanger.java b/src/main/java/org/jabref/logic/bst/util/BstCaseChanger.java similarity index 83% rename from src/main/java/org/jabref/logic/bst/BibtexCaseChanger.java rename to src/main/java/org/jabref/logic/bst/util/BstCaseChanger.java index e05ce998e80..dd3e7c7b05c 100644 --- a/src/main/java/org/jabref/logic/bst/BibtexCaseChanger.java +++ b/src/main/java/org/jabref/logic/bst/util/BstCaseChanger.java @@ -1,4 +1,4 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import java.util.Locale; import java.util.Optional; @@ -6,9 +6,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public final class BibtexCaseChanger { +public final class BstCaseChanger { - private static final Logger LOGGER = LoggerFactory.getLogger(BibtexCaseChanger.class); + private static final Logger LOGGER = LoggerFactory.getLogger(BstCaseChanger.class); // stores whether the char before the current char was a colon private boolean prevColon = true; @@ -16,7 +16,7 @@ public final class BibtexCaseChanger { // global variable to store the current brace level private int braceLevel; - public enum FORMAT_MODE { + public enum FormatMode { // First character and character after a ":" as upper case - everything else in lower case. Obey {}. TITLE_LOWERS('t'), @@ -40,7 +40,7 @@ public enum FORMAT_MODE { private final char asChar; - FORMAT_MODE(char asChar) { + FormatMode(char asChar) { this.asChar = asChar; } @@ -53,17 +53,21 @@ public char asChar() { * * @throws IllegalArgumentException if char is not 't', 'l', 'u' */ - public static FORMAT_MODE getFormatModeForBSTFormat(final char bstFormat) { - for (FORMAT_MODE mode : FORMAT_MODE.values()) { + public static FormatMode of(final char bstFormat) { + for (FormatMode mode : FormatMode.values()) { if (mode.asChar == bstFormat) { return mode; } } throw new IllegalArgumentException(); } + + public static FormatMode of(final String bstFormat) { + return of(bstFormat.toLowerCase(Locale.ROOT).charAt(0)); + } } - private BibtexCaseChanger() { + private BstCaseChanger() { } /** @@ -72,11 +76,11 @@ private BibtexCaseChanger() { * @param s the string to handle * @param format the format */ - public static String changeCase(String s, FORMAT_MODE format) { - return (new BibtexCaseChanger()).doChangeCase(s, format); + public static String changeCase(String s, FormatMode format) { + return (new BstCaseChanger()).doChangeCase(s, format); } - private String doChangeCase(String s, FORMAT_MODE format) { + private String doChangeCase(String s, FormatMode format) { char[] c = s.toCharArray(); StringBuilder sb = new StringBuilder(); @@ -93,7 +97,7 @@ private String doChangeCase(String s, FORMAT_MODE format) { i++; continue; } - if ((format == FORMAT_MODE.TITLE_LOWERS) && ((i == 0) || (prevColon && Character.isWhitespace(c[i - 1])))) { + if ((format == FormatMode.TITLE_LOWERS) && ((i == 0) || (prevColon && Character.isWhitespace(c[i - 1])))) { sb.append('{'); i++; prevColon = false; @@ -136,12 +140,9 @@ private String doChangeCase(String s, FORMAT_MODE format) { * is other stuff, too, between braces, but it doesn't try to do anything * special with |colon|s. * - * @param c * @param start the current position. It points to the opening brace - * @param format - * @return */ - private int convertSpecialChar(StringBuilder sb, char[] c, int start, FORMAT_MODE format) { + private int convertSpecialChar(StringBuilder sb, char[] c, int start, FormatMode format) { int i = start; sb.append(c[i]); @@ -152,7 +153,7 @@ private int convertSpecialChar(StringBuilder sb, char[] c, int start, FORMAT_MOD i++; // skip over the |backslash| - Optional s = BibtexCaseChanger.findSpecialChar(c, i); + Optional s = BstCaseChanger.findSpecialChar(c, i); if (s.isPresent()) { i = convertAccented(c, i, s.get(), sb, format); } @@ -174,14 +175,9 @@ private int convertSpecialChar(StringBuilder sb, char[] c, int start, FORMAT_MOD * up) and append the result to the stringBuffer, return the updated * position. * - * @param c - * @param start - * @param s - * @param sb - * @param format * @return the new position */ - private int convertAccented(char[] c, int start, String s, StringBuilder sb, FORMAT_MODE format) { + private int convertAccented(char[] c, int start, String s, StringBuilder sb, FormatMode format) { int pos = start; pos += s.length(); @@ -214,29 +210,27 @@ private int convertAccented(char[] c, int start, String s, StringBuilder sb, FOR return pos; } - private int convertNonControl(char[] c, int start, StringBuilder sb, FORMAT_MODE format) { + private int convertNonControl(char[] c, int start, StringBuilder sb, FormatMode format) { int pos = start; switch (format) { - case TITLE_LOWERS: - case ALL_LOWERS: + case TITLE_LOWERS, ALL_LOWERS -> { sb.append(Character.toLowerCase(c[pos])); pos++; - break; - case ALL_UPPERS: + } + case ALL_UPPERS -> { sb.append(Character.toUpperCase(c[pos])); pos++; - break; - default: - LOGGER.info("convertNonControl - Unknown format: " + format); - break; + } + default -> + LOGGER.info("convertNonControl - Unknown format: " + format); } return pos; } - private int convertCharIfBraceLevelIsZero(char[] c, int start, StringBuilder sb, FORMAT_MODE format) { + private int convertCharIfBraceLevelIsZero(char[] c, int start, StringBuilder sb, FormatMode format) { int i = start; switch (format) { - case TITLE_LOWERS: + case TITLE_LOWERS -> { if ((i == 0) || (prevColon && Character.isWhitespace(c[i - 1]))) { sb.append(c[i]); } else { @@ -247,16 +241,13 @@ private int convertCharIfBraceLevelIsZero(char[] c, int start, StringBuilder sb, } else if (!Character.isWhitespace(c[i])) { prevColon = false; } - break; - case ALL_LOWERS: - sb.append(Character.toLowerCase(c[i])); - break; - case ALL_UPPERS: - sb.append(Character.toUpperCase(c[i])); - break; - default: - LOGGER.info("convertCharIfBraceLevelIsZero - Unknown format: " + format); - break; + } + case ALL_LOWERS -> + sb.append(Character.toLowerCase(c[i])); + case ALL_UPPERS -> + sb.append(Character.toUpperCase(c[i])); + default -> + LOGGER.info("convertCharIfBraceLevelIsZero - Unknown format: " + format); } i++; return i; diff --git a/src/main/java/org/jabref/logic/bst/BibtexNameFormatter.java b/src/main/java/org/jabref/logic/bst/util/BstNameFormatter.java similarity index 76% rename from src/main/java/org/jabref/logic/bst/BibtexNameFormatter.java rename to src/main/java/org/jabref/logic/bst/util/BstNameFormatter.java index aecf571bdf4..c4986dc12dd 100644 --- a/src/main/java/org/jabref/logic/bst/BibtexNameFormatter.java +++ b/src/main/java/org/jabref/logic/bst/util/BstNameFormatter.java @@ -1,13 +1,17 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import java.util.Arrays; import java.util.Locale; import java.util.Optional; import java.util.stream.Collectors; +import org.jabref.logic.bst.BstVMException; import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * From Bibtex: * @@ -24,9 +28,10 @@ * Sounds easy - is a nightmare... X-( * */ -public class BibtexNameFormatter { +public class BstNameFormatter { + private static final Logger LOGGER = LoggerFactory.getLogger(BstNameFormatter.class); - private BibtexNameFormatter() { + private BstNameFormatter() { } /** @@ -35,23 +40,18 @@ private BibtexNameFormatter() { * @param authorsNameList The string from an author field * @param whichName index of the list, starting with 1 * @param formatString TODO - * @param warn collects the warnings, may-be-null - * @return */ - public static String formatName(String authorsNameList, int whichName, String formatString, Warn warn) { + public static String formatName(String authorsNameList, int whichName, String formatString) { AuthorList al = AuthorList.parse(authorsNameList); if ((whichName < 1) && (whichName > al.getNumberOfAuthors())) { - warn.warn("AuthorList " + authorsNameList + " does not contain an author with number " + whichName); + LOGGER.warn("AuthorList {} does not contain an author with number {}", authorsNameList, whichName); return ""; } - return BibtexNameFormatter.formatName(al.getAuthor(whichName - 1), formatString, warn); + return BstNameFormatter.formatName(al.getAuthor(whichName - 1), formatString); } - /** - * @param warn collects the warnings, may-be-null - */ - public static String formatName(Author author, String format, Warn warn) { + public static String formatName(Author author, String format) { StringBuilder sb = new StringBuilder(); char[] c = format.toCharArray(); @@ -81,11 +81,7 @@ public static String formatName(Author author, String format, Warn warn) { } if ((braceLevel == 1) && Character.isLetter(c[i])) { if ("fvlj".indexOf(c[i]) == -1) { - if (warn != null) { - warn.warn( - "Format string in format.name$ may only contain fvlj on brace level 1 in group " - + group + ": " + format); - } + LOGGER.warn("Format string in format.name$ may only contain fvlj on brace level 1 in group {}: {}", group, format); } else { level1Chars.append(c[i]); } @@ -99,31 +95,26 @@ public static String formatName(Author author, String format, Warn warn) { continue; } - if ((control.length() > 2) && (warn != null)) { - warn.warn("Format string in format.name$ may only be one or two character long on brace level 1 in group " + group + ": " + format); + if ((control.length() > 2)) { + LOGGER.warn("Format string in format.name$ may only be one or two character long on brace level 1 in group {}: {}", group, format); } char type = control.charAt(0); - Optional tokenS; - switch (type) { - case 'f': - tokenS = author.getFirst(); - break; - case 'v': - tokenS = author.getVon(); - break; - case 'l': - tokenS = author.getLast(); - break; - case 'j': - tokenS = author.getJr(); - break; - default: - throw new VMException("Internal error"); - } - - if (!tokenS.isPresent()) { + Optional tokenS = switch (type) { + case 'f' -> + author.getFirst(); + case 'v' -> + author.getVon(); + case 'l' -> + author.getLast(); + case 'j' -> + author.getJr(); + default -> + throw new BstVMException("Internal error"); + }; + + if (tokenS.isEmpty()) { i++; continue; } @@ -135,9 +126,7 @@ public static String formatName(Author author, String format, Warn warn) { if (control.charAt(1) == control.charAt(0)) { abbreviateThatIsSingleLetter = false; } else { - if (warn != null) { - warn.warn("Format string in format.name$ may only contain one type of vlfj on brace level 1 in group " + group + ": " + format); - } + LOGGER.warn("Format string in format.name$ may only contain one type of vlfj on brace level 1 in group {}: {}", group, format); } } @@ -162,7 +151,7 @@ public static String formatName(Author author, String format, Warn warn) { } if (((j + 1) < d.length) && (d[j + 1] == '{')) { StringBuilder interTokenSb = new StringBuilder(); - j = BibtexNameFormatter.consumeToMatchingBrace(interTokenSb, d, j + 1); + j = BstNameFormatter.consumeToMatchingBrace(interTokenSb, d, j + 1); interToken = interTokenSb.substring(1, interTokenSb.length() - 1); } @@ -171,7 +160,7 @@ public static String formatName(Author author, String format, Warn warn) { if (abbreviateThatIsSingleLetter) { String[] dashes = token.split("-"); - token = Arrays.asList(dashes).stream().map(BibtexNameFormatter::getFirstCharOfString) + token = Arrays.stream(dashes).map(BstNameFormatter::getFirstCharOfString) .collect(Collectors.joining(".-")); } @@ -187,7 +176,7 @@ public static String formatName(Author author, String format, Warn warn) { // No clue what this means (What the hell are tokens anyway??? // if (lex_class[name_sep_char[cur_token]] = sep_char) then // append_ex_buf_char_and_check (name_sep_char[cur_token]) - if ((k == (tokens.length - 2)) || (BibtexNameFormatter.numberOfChars(sb.substring(groupStart, sb.length()), 3) < 3)) { + if ((k == (tokens.length - 2)) || (BstNameFormatter.numberOfChars(sb.substring(groupStart, sb.length()), 3) < 3)) { sb.append('~'); } else { sb.append(' '); @@ -212,7 +201,7 @@ public static String formatName(Author author, String format, Warn warn) { if (sb.length() > 0) { boolean noDisTie = false; if ((sb.charAt(sb.length() - 1) == '~') && - ((BibtexNameFormatter.numberOfChars(sb.substring(groupStart, sb.length()), 4) >= 4) || + ((BstNameFormatter.numberOfChars(sb.substring(groupStart, sb.length()), 4) >= 4) || ((sb.length() > 1) && (noDisTie = sb.charAt(sb.length() - 2) == '~')))) { sb.deleteCharAt(sb.length() - 1); if (!noDisTie) { @@ -221,16 +210,14 @@ public static String formatName(Author author, String format, Warn warn) { } } } else if (c[i] == '}') { - if (warn != null) { - warn.warn("Unmatched brace in format string: " + format); - } + LOGGER.warn("Unmatched brace in format string: {}", format); } else { sb.append(c[i]); // verbatim } i++; } - if ((braceLevel != 0) && (warn != null)) { - warn.warn("Unbalanced brace in format string for nameFormat: " + format); + if ((braceLevel != 0)) { + LOGGER.warn("Unbalanced brace in format string for nameFormat: {}", format); } return sb.toString(); @@ -268,7 +255,7 @@ public static String getFirstCharOfString(String s) { } if ((c[i] == '{') && ((i + 1) < c.length) && (c[i + 1] == '\\')) { StringBuilder sb = new StringBuilder(); - BibtexNameFormatter.consumeToMatchingBrace(sb, c, i); + BstNameFormatter.consumeToMatchingBrace(sb, c, i); return sb.toString(); } } diff --git a/src/main/java/org/jabref/logic/bst/BibtexPurify.java b/src/main/java/org/jabref/logic/bst/util/BstPurifier.java similarity index 77% rename from src/main/java/org/jabref/logic/bst/BibtexPurify.java rename to src/main/java/org/jabref/logic/bst/util/BstPurifier.java index 08cb14db207..90818a0749f 100644 --- a/src/main/java/org/jabref/logic/bst/BibtexPurify.java +++ b/src/main/java/org/jabref/logic/bst/util/BstPurifier.java @@ -1,4 +1,7 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @@ -10,17 +13,13 @@ * pushes the null string. * */ -public class BibtexPurify { +public class BstPurifier { + private static final Logger LOGGER = LoggerFactory.getLogger(BstPurifier.class); - private BibtexPurify() { + private BstPurifier() { } - /** - * @param toPurify - * @param warn may-be-null - * @return - */ - public static String purify(String toPurify, Warn warn) { + public static String purify(String toPurify) { StringBuilder sb = new StringBuilder(); char[] cs = toPurify.toCharArray(); @@ -41,7 +40,7 @@ public static String purify(String toPurify, Warn warn) { i++; // skip brace while ((i < n) && (braceLevel > 0)) { i++; // skip backslash - BibtexCaseChanger.findSpecialChar(cs, i).ifPresent(sb::append); + BstCaseChanger.findSpecialChar(cs, i).ifPresent(sb::append); while ((i < n) && Character.isLetter(cs[i])) { i++; @@ -63,15 +62,13 @@ public static String purify(String toPurify, Warn warn) { if (braceLevel > 0) { braceLevel--; } else { - if (warn != null) { - warn.warn("Unbalanced brace in string for purify$: " + toPurify); - } + LOGGER.warn("Unbalanced brace in string for purify$: {}", toPurify); } } i++; } - if ((braceLevel != 0) && (warn != null)) { - warn.warn("Unbalanced brace in string for purify$: " + toPurify); + if ((braceLevel != 0)) { + LOGGER.warn("Unbalanced brace in string for purify$: {}", toPurify); } return sb.toString(); diff --git a/src/main/java/org/jabref/logic/bst/BibtexTextPrefix.java b/src/main/java/org/jabref/logic/bst/util/BstTextPrefixer.java similarity index 84% rename from src/main/java/org/jabref/logic/bst/BibtexTextPrefix.java rename to src/main/java/org/jabref/logic/bst/util/BstTextPrefixer.java index 721838e29fe..31cbc71de9b 100644 --- a/src/main/java/org/jabref/logic/bst/BibtexTextPrefix.java +++ b/src/main/java/org/jabref/logic/bst/util/BstTextPrefixer.java @@ -1,4 +1,7 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The |built_in| function {\.{text.prefix\$}} pops the top two literals (the @@ -14,15 +17,13 @@ * complains and pushes the null string. * */ -public class BibtexTextPrefix { +public class BstTextPrefixer { + private static final Logger LOGGER = LoggerFactory.getLogger(BstTextPrefixer.class); - private BibtexTextPrefix() { + private BstTextPrefixer() { } - /** - * @param warn may-be-null - */ - public static String textPrefix(int inNumOfChars, String toPrefix, Warn warn) { + public static String textPrefix(int inNumOfChars, String toPrefix) { int numOfChars = inNumOfChars; StringBuilder sb = new StringBuilder(); @@ -53,15 +54,13 @@ public static String textPrefix(int inNumOfChars, String toPrefix, Warn warn) { if (braceLevel > 0) { braceLevel--; } else { - if (warn != null) { - warn.warn("Unbalanced brace in string for purify$: " + toPrefix); - } + LOGGER.warn("Unbalanced brace in string for purify$: {}", toPrefix); } } else { numOfChars--; } } - sb.append(toPrefix.substring(0, i)); + sb.append(toPrefix, 0, i); while (braceLevel > 0) { sb.append('}'); braceLevel--; diff --git a/src/main/java/org/jabref/logic/bst/util/BstWidthCalculator.java b/src/main/java/org/jabref/logic/bst/util/BstWidthCalculator.java new file mode 100644 index 00000000000..c286fe1497b --- /dev/null +++ b/src/main/java/org/jabref/logic/bst/util/BstWidthCalculator.java @@ -0,0 +1,241 @@ +package org.jabref.logic.bst.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * The |built_in| function {\.{purify\$}} pops the top (string) literal, removes + * nonalphanumeric characters except for |white_space| and |sep_char| characters + * (these get converted to a |space|) and removes certain alphabetic characters + * contained in the control sequences associated with a special character, and + * pushes the resulting string. If the literal isn't a string, it complains and + * pushes the null string. + * + */ +public class BstWidthCalculator { + + private static final Logger LOGGER = LoggerFactory.getLogger(BstWidthCalculator.class); + + /* + * Quoted from Bibtex: + * + * Now we initialize the system-dependent |char_width| array, for which + * |space| is the only |white_space| character given a nonzero printing + * width. The widths here are taken from Stanford's June~'87 $cmr10$~font + * and represent hundredths of a point (rounded), but since they're used + * only for relative comparisons, the units have no meaning. + */ + + private static int[] widths; + + static { + if (BstWidthCalculator.widths == null) { + BstWidthCalculator.widths = new int[128]; + + for (int i = 0; i < 128; i++) { + BstWidthCalculator.widths[i] = 0; + } + BstWidthCalculator.widths[32] = 278; + BstWidthCalculator.widths[33] = 278; + BstWidthCalculator.widths[34] = 500; + BstWidthCalculator.widths[35] = 833; + BstWidthCalculator.widths[36] = 500; + BstWidthCalculator.widths[37] = 833; + BstWidthCalculator.widths[38] = 778; + BstWidthCalculator.widths[39] = 278; + BstWidthCalculator.widths[40] = 389; + BstWidthCalculator.widths[41] = 389; + BstWidthCalculator.widths[42] = 500; + BstWidthCalculator.widths[43] = 778; + BstWidthCalculator.widths[44] = 278; + BstWidthCalculator.widths[45] = 333; + BstWidthCalculator.widths[46] = 278; + BstWidthCalculator.widths[47] = 500; + BstWidthCalculator.widths[48] = 500; + BstWidthCalculator.widths[49] = 500; + BstWidthCalculator.widths[50] = 500; + BstWidthCalculator.widths[51] = 500; + BstWidthCalculator.widths[52] = 500; + BstWidthCalculator.widths[53] = 500; + BstWidthCalculator.widths[54] = 500; + BstWidthCalculator.widths[55] = 500; + BstWidthCalculator.widths[56] = 500; + BstWidthCalculator.widths[57] = 500; + BstWidthCalculator.widths[58] = 278; + BstWidthCalculator.widths[59] = 278; + BstWidthCalculator.widths[60] = 278; + BstWidthCalculator.widths[61] = 778; + BstWidthCalculator.widths[62] = 472; + BstWidthCalculator.widths[63] = 472; + BstWidthCalculator.widths[64] = 778; + BstWidthCalculator.widths[65] = 750; + BstWidthCalculator.widths[66] = 708; + BstWidthCalculator.widths[67] = 722; + BstWidthCalculator.widths[68] = 764; + BstWidthCalculator.widths[69] = 681; + BstWidthCalculator.widths[70] = 653; + BstWidthCalculator.widths[71] = 785; + BstWidthCalculator.widths[72] = 750; + BstWidthCalculator.widths[73] = 361; + BstWidthCalculator.widths[74] = 514; + BstWidthCalculator.widths[75] = 778; + BstWidthCalculator.widths[76] = 625; + BstWidthCalculator.widths[77] = 917; + BstWidthCalculator.widths[78] = 750; + BstWidthCalculator.widths[79] = 778; + BstWidthCalculator.widths[80] = 681; + BstWidthCalculator.widths[81] = 778; + BstWidthCalculator.widths[82] = 736; + BstWidthCalculator.widths[83] = 556; + BstWidthCalculator.widths[84] = 722; + BstWidthCalculator.widths[85] = 750; + BstWidthCalculator.widths[86] = 750; + BstWidthCalculator.widths[87] = 1028; + BstWidthCalculator.widths[88] = 750; + BstWidthCalculator.widths[89] = 750; + BstWidthCalculator.widths[90] = 611; + BstWidthCalculator.widths[91] = 278; + BstWidthCalculator.widths[92] = 500; + BstWidthCalculator.widths[93] = 278; + BstWidthCalculator.widths[94] = 500; + BstWidthCalculator.widths[95] = 278; + BstWidthCalculator.widths[96] = 278; + BstWidthCalculator.widths[97] = 500; + BstWidthCalculator.widths[98] = 556; + BstWidthCalculator.widths[99] = 444; + BstWidthCalculator.widths[100] = 556; + BstWidthCalculator.widths[101] = 444; + BstWidthCalculator.widths[102] = 306; + BstWidthCalculator.widths[103] = 500; + BstWidthCalculator.widths[104] = 556; + BstWidthCalculator.widths[105] = 278; + BstWidthCalculator.widths[106] = 306; + BstWidthCalculator.widths[107] = 528; + BstWidthCalculator.widths[108] = 278; + BstWidthCalculator.widths[109] = 833; + BstWidthCalculator.widths[110] = 556; + BstWidthCalculator.widths[111] = 500; + BstWidthCalculator.widths[112] = 556; + BstWidthCalculator.widths[113] = 528; + BstWidthCalculator.widths[114] = 392; + BstWidthCalculator.widths[115] = 394; + BstWidthCalculator.widths[116] = 389; + BstWidthCalculator.widths[117] = 556; + BstWidthCalculator.widths[118] = 528; + BstWidthCalculator.widths[119] = 722; + BstWidthCalculator.widths[120] = 528; + BstWidthCalculator.widths[121] = 528; + BstWidthCalculator.widths[122] = 444; + BstWidthCalculator.widths[123] = 500; + BstWidthCalculator.widths[124] = 1000; + BstWidthCalculator.widths[125] = 500; + BstWidthCalculator.widths[126] = 500; + } + } + + private BstWidthCalculator() { + } + + private static int getSpecialCharWidth(char[] c, int pos) { + if ((pos + 1) < c.length) { + if ((c[pos] == 'o') && (c[pos + 1] == 'e')) { + return 778; + } + if ((c[pos] == 'O') && (c[pos + 1] == 'E')) { + return 1014; + } + if ((c[pos] == 'a') && (c[pos + 1] == 'e')) { + return 722; + } + if ((c[pos] == 'A') && (c[pos + 1] == 'E')) { + return 903; + } + if ((c[pos] == 's') && (c[pos + 1] == 's')) { + return 500; + } + } + return BstWidthCalculator.getCharWidth(c[pos]); + } + + public static int getCharWidth(char c) { + if ((c >= 0) && (c < 128)) { + return BstWidthCalculator.widths[c]; + } else { + return 0; + } + } + + public static int width(String toMeasure) { + /* + * From Bibtex: We use the natural width for all but special characters, + * and we complain if the string isn't brace-balanced. + */ + + int i = 0; + int n = toMeasure.length(); + int braceLevel = 0; + char[] c = toMeasure.toCharArray(); + int result = 0; + + /* + * From Bibtex: + * + * We use the natural widths of all characters except that some + * characters have no width: braces, control sequences (except for the + * usual 13 accented and foreign characters, whose widths are given in + * the next module), and |white_space| following control sequences (even + * a null control sequence). + * + */ + while (i < n) { + if (c[i] == '{') { + braceLevel++; + if ((braceLevel == 1) && ((i + 1) < n) && (c[i + 1] == '\\')) { + i++; // skip brace + while ((i < n) && (braceLevel > 0)) { + i++; // skip backslash + + int afterBackslash = i; + while ((i < n) && Character.isLetter(c[i])) { + i++; + } + if ((i < n) && (i == afterBackslash)) { + i++; // Skip non-alpha control seq + } else { + if (BstCaseChanger.findSpecialChar(c, afterBackslash).isPresent()) { + result += BstWidthCalculator.getSpecialCharWidth(c, afterBackslash); + } + } + while ((i < n) && Character.isWhitespace(c[i])) { + i++; + } + while ((i < n) && (braceLevel > 0) && (c[i] != '\\')) { + if (c[i] == '}') { + braceLevel--; + } else if (c[i] == '{') { + braceLevel++; + } else { + result += BstWidthCalculator.getCharWidth(c[i]); + } + i++; + } + } + continue; + } + } else if (c[i] == '}') { + if (braceLevel > 0) { + braceLevel--; + } else { + LOGGER.warn("Too many closing braces in string: " + toMeasure); + } + } + result += BstWidthCalculator.getCharWidth(c[i]); + i++; + } + if (braceLevel > 0) { + LOGGER.warn("No enough closing braces in string: " + toMeasure); + } + return result; + } +} diff --git a/src/main/java/org/jabref/logic/layout/format/NameFormatter.java b/src/main/java/org/jabref/logic/layout/format/NameFormatter.java index 919ff4df2d5..e8812235435 100644 --- a/src/main/java/org/jabref/logic/layout/format/NameFormatter.java +++ b/src/main/java/org/jabref/logic/layout/format/NameFormatter.java @@ -5,7 +5,7 @@ import java.util.Map; import java.util.Objects; -import org.jabref.logic.bst.BibtexNameFormatter; +import org.jabref.logic.bst.util.BstNameFormatter; import org.jabref.logic.layout.LayoutFormatter; import org.jabref.model.entry.AuthorList; @@ -86,7 +86,7 @@ private static String format(String toFormat, AuthorList al, String[] formats) { for (int i = 1; i <= al.getNumberOfAuthors(); i++) { for (int j = 1; j < formats.length; j += 2) { if ("*".equals(formats[j])) { - sb.append(BibtexNameFormatter.formatName(toFormat, i, formats[j + 1], null)); + sb.append(BstNameFormatter.formatName(toFormat, i, formats[j + 1])); break; } else { String[] range = formats[j].split("\\.\\."); @@ -112,7 +112,7 @@ private static String format(String toFormat, AuthorList al, String[] formats) { } if ((s <= i) && (i <= e)) { - sb.append(BibtexNameFormatter.formatName(toFormat, i, formats[j + 1], null)); + sb.append(BstNameFormatter.formatName(toFormat, i, formats[j + 1])); break; } } diff --git a/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java b/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java new file mode 100644 index 00000000000..1b63e26cbb3 --- /dev/null +++ b/src/test/java/org/jabref/logic/bst/BstFunctionsTest.java @@ -0,0 +1,666 @@ +package org.jabref.logic.bst; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.jabref.logic.bst.util.BstCaseChangersTest; +import org.jabref.logic.bst.util.BstNameFormatterTest; +import org.jabref.logic.bst.util.BstPurifierTest; +import org.jabref.logic.bst.util.BstTextPrefixerTest; +import org.jabref.logic.bst.util.BstWidthCalculatorTest; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import org.antlr.v4.runtime.RecognitionException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * For additional tests see for + *
    + *
  • purify: {@link BstPurifierTest}
  • + *
  • width: {@link BstWidthCalculatorTest}
  • + *
  • format.name: {@link BstNameFormatterTest}
  • + *
  • change.case: {@link BstCaseChangersTest}
  • + *
  • prefix: {@link BstTextPrefixerTest}
  • + *
+ */ +class BstFunctionsTest { + @Test + public void testCompareFunctions() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { test.compare } { + #5 #5 = % TRUE + #1 #2 = % FALSE + #3 #4 < % TRUE + #4 #3 < % FALSE + #4 #4 < % FALSE + #3 #4 > % FALSE + #4 #3 > % TRUE + #4 #4 > % FALSE + "H" "H" = % TRUE + "H" "Ha" = % FALSE + } + EXECUTE { test.compare } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testArithmeticFunctions() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { test } { + #1 #1 + % 2 + #5 #2 - % 3 + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(3, vm.getStack().pop()); + assertEquals(2, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testArithmeticFunctionTypeMismatch() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { test } { + #1 "HELLO" + % Should throw exception + } + EXECUTE { test } + """); + + assertThrows(BstVMException.class, () -> vm.render(Collections.emptyList())); + } + + @Test + public void testStringOperations() throws RecognitionException { + // Test for concat (*) and add.period + BstVM vm = new BstVM(""" + FUNCTION { test } { + "H" "ello" * % Hello + "Johnny" add.period$ % Johnny. + "Johnny." add.period$ % Johnny. + "Johnny!" add.period$ % Johnny! + "Johnny?" add.period$ % Johnny? + "Johnny} }}}" add.period$ % Johnny.} + "Johnny!}" add.period$ % Johnny!} + "Johnny?}" add.period$ % Johnny?} + "Johnny.}" add.period$ % Johnny.} + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("Johnny.}", vm.getStack().pop()); + assertEquals("Johnny?}", vm.getStack().pop()); + assertEquals("Johnny!}", vm.getStack().pop()); + assertEquals("Johnny.}", vm.getStack().pop()); + assertEquals("Johnny?", vm.getStack().pop()); + assertEquals("Johnny!", vm.getStack().pop()); + assertEquals("Johnny.", vm.getStack().pop()); + assertEquals("Johnny.", vm.getStack().pop()); + assertEquals("Hello", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testMissing() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { title } { } { } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + READ + SORT + FUNCTION { test } { title missing$ cite$ } + ITERATE { test } + """); + List testEntries = List.of( + BstVMTest.defaultTestEntry(), + new BibEntry(StandardEntryType.Article) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "No title")); + + vm.render(testEntries); + + assertEquals("test", vm.getStack().pop()); // cite + assertEquals(BstVM.TRUE, vm.getStack().pop()); // missing title + assertEquals("canh05", vm.getStack().pop()); // cite + assertEquals(BstVM.FALSE, vm.getStack().pop()); // missing title + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testNumNames() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { test } { + "Johnny Foo { and } Mary Bar" num.names$ + "Johnny Foo and Mary Bar" num.names$ + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(2, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testSubstring() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { test } { + "123456789" #2 #1 substring$ % 2 + "123456789" #4 global.max$ substring$ % 456789 + "123456789" #1 #9 substring$ % 123456789 + "123456789" #1 #10 substring$ % 123456789 + "123456789" #1 #99 substring$ % 123456789 + "123456789" #-7 #3 substring$ % 123 + "123456789" #-1 #1 substring$ % 9 + "123456789" #-1 #3 substring$ % 789 + "123456789" #-2 #2 substring$ % 78 + } EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("78", vm.getStack().pop()); + assertEquals("789", vm.getStack().pop()); + assertEquals("9", vm.getStack().pop()); + assertEquals("123", vm.getStack().pop()); + assertEquals("123456789", vm.getStack().pop()); + assertEquals("123456789", vm.getStack().pop()); + assertEquals("123456789", vm.getStack().pop()); + assertEquals("456789", vm.getStack().pop()); + assertEquals("2", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testEmpty() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { title } { } { } + READ + STRINGS { s } + FUNCTION { test } { + s empty$ % TRUE + "" empty$ % TRUE + " " empty$ % TRUE + title empty$ % TRUE + " HALLO " empty$ % FALSE + } + ITERATE { test } + """); + List testEntry = List.of(new BibEntry(StandardEntryType.Article)); + + vm.render(testEntry); + + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testFormatNameStatic() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { format }{ "Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin" #1 "{vv~}{ll}{, jj}{, f}?" format.name$ } + EXECUTE { format } + """); + List v = Collections.emptyList(); + + vm.render(v); + + assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testFormatNameInEntries() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { author } { } { } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + READ + SORT + FUNCTION { format }{ author #2 "{vv~}{ll}{, jj}{, f}?" format.name$ } + ITERATE { format } + """); + List testEntries = List.of( + BstVMTest.defaultTestEntry(), + new BibEntry(StandardEntryType.Book) + .withCitationKey("test") + .withField(StandardField.AUTHOR, "Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin")); + + vm.render(testEntries); + + assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); + assertEquals("Annabi, H?", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testChangeCase() throws RecognitionException { + BstVM vm = new BstVM(""" + STRINGS { title } + READ + FUNCTION { format.title } { + duplicate$ empty$ + { pop$ "" } + { "t" change.case$ } + if$ + } + FUNCTION { test } { + "hello world" "u" change.case$ format.title + "Hello World" format.title + "" format.title + "{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase" "u" change.case$ format.title + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase", + vm.getStack().pop()); + assertEquals("", vm.getStack().pop()); + assertEquals("Hello world", vm.getStack().pop()); + assertEquals("Hello world", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testTextLength() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { test } { + "hello world" text.length$ % 11 + "Hello {W}orld" text.length$ % 11 + "" text.length$ % 0 + "{A}{D}/{Cycle}" text.length$ % 8 + "{\\This is one character}" text.length$ % 1 + "{\\This {is} {one} {c{h}}aracter as well}" text.length$ % 1 + "{\\And this too" text.length$ % 1 + "These are {\\11}" text.length$ % 11 + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(11, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(8, vm.getStack().pop()); + assertEquals(0, vm.getStack().pop()); + assertEquals(11, vm.getStack().pop()); + assertEquals(11, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testIntToStr() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { test } { #3 int.to.str$ #9999 int.to.str$ } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("9999", vm.getStack().pop()); + assertEquals("3", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testChrToInt() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { test } { "H" chr.to.int$ } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(72, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testChrToIntIntToChr() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { test } { "H" chr.to.int$ int.to.chr$ } + EXECUTE {test} + """); + + vm.render(Collections.emptyList()); + + assertEquals("H", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testType() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { } { } { } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + SORT + FUNCTION { test } { type$ } + ITERATE { test } + """); + List testEntries = List.of( + new BibEntry(StandardEntryType.Article).withCitationKey("a"), + new BibEntry(StandardEntryType.Book).withCitationKey("b"), + new BibEntry(StandardEntryType.Misc).withCitationKey("c"), + new BibEntry(StandardEntryType.InProceedings).withCitationKey("d")); + + vm.render(testEntries); + + assertEquals("inproceedings", vm.getStack().pop()); + assertEquals("misc", vm.getStack().pop()); + assertEquals("book", vm.getStack().pop()); + assertEquals("article", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testCallType() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { title } { } { } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + READ + SORT + FUNCTION { inproceedings }{ "InProceedings called on " title * } + FUNCTION { book }{ "Book called on " title * } + ITERATE { call.type$ } + """); + List testEntries = List.of( + BstVMTest.defaultTestEntry(), + new BibEntry(StandardEntryType.Book) + .withCitationKey("test") + .withField(StandardField.TITLE, "Test")); + + vm.render(testEntries); + + assertEquals("Book called on Test", vm.getStack().pop()); + assertEquals( + "InProceedings called on Effective work practices for floss development: A model and propositions", + vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testSwap() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { a } { #3 "Hallo" swap$ } + EXECUTE { a } + """); + + List v = Collections.emptyList(); + vm.render(v); + + assertEquals(3, vm.getStack().pop()); + assertEquals("Hallo", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + void testAssignFunction() { + BstVM vm = new BstVM(""" + INTEGERS { test.var } + FUNCTION { test.func } { #1 'test.var := } + EXECUTE { test.func } + """); + + vm.render(Collections.emptyList()); + + Map functions = vm.latestContext.functions(); + assertTrue(functions.containsKey("test.func")); + assertNotNull(functions.get("test.func")); + assertEquals(1, vm.latestContext.integers().get("test.var")); + } + + @Test + void testSimpleIf() { + BstVM vm = new BstVM(""" + FUNCTION { path1 } { #1 } + FUNCTION { path0 } { #0 } + FUNCTION { test } { + #1 path1 path0 if$ + #0 path1 path0 if$ + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(0, vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + void testSimpleWhile() { + BstVM vm = new BstVM(""" + INTEGERS { i } + FUNCTION { test } { + #3 'i := + { i } + { + i + i #1 - + 'i := + } + while$ + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(1, vm.getStack().pop()); + assertEquals(2, vm.getStack().pop()); + assertEquals(3, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testNestedControlFunctions() throws RecognitionException { + BstVM vm = new BstVM(""" + STRINGS { t } + FUNCTION { not } { { #0 } { #1 } if$ } + FUNCTION { n.dashify } { + "HELLO-WORLD" 't := + "" + { t empty$ not } % while + { + t #1 #1 substring$ "-" = % if + { + t #1 #2 substring$ "--" = not % if + { + "--" * + t #2 global.max$ substring$ 't := + } + { + { t #1 #1 substring$ "-" = } % while + { + "-" * + t #2 global.max$ substring$ 't := + } + while$ + } + if$ + } + { + t #1 #1 substring$ * + t #2 global.max$ substring$ 't := + } + if$ + } + while$ + } + EXECUTE { n.dashify } + """); + List v = Collections.emptyList(); + + vm.render(v); + + assertEquals(1, vm.getStack().size()); + assertEquals("HELLO--WORLD", vm.getStack().pop()); + } + + @Test + public void testLogic() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { not } { { #0 } { #1 } if$ } + FUNCTION { and } { 'skip$ { pop$ #0 } if$ } + FUNCTION { or } { { pop$ #1 } 'skip$ if$ } + FUNCTION { test } { + #1 #1 and + #0 #1 and + #1 #0 and + #0 #0 and + #0 not + #1 not + #1 #1 or + #0 #1 or + #1 #0 or + #0 #0 or + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.FALSE, vm.getStack().pop()); + assertEquals(BstVM.TRUE, vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + /** + * See also {@link BstWidthCalculatorTest} + */ + + @Test + public void testWidth() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { address author title type } { } { label } + STRINGS { longest.label } + INTEGERS { number.label longest.label.width } + FUNCTION { initialize.longest.label } { + "" 'longest.label := + #1 'number.label := + #0 'longest.label.width := + } + FUNCTION {longest.label.pass} { + number.label int.to.str$ 'label := + number.label #1 + 'number.label := + label width$ longest.label.width > + { + label 'longest.label := + label width$ 'longest.label.width := + } + 'skip$ + if$ + } + EXECUTE { initialize.longest.label } + ITERATE { longest.label.pass } + FUNCTION { begin.bib } { + preamble$ empty$ + 'skip$ + { preamble$ write$ newline$ } + if$ + "\\begin{thebibliography}{" longest.label * "}" * + } + EXECUTE {begin.bib} + """); + + List testEntries = List.of(BstVMTest.defaultTestEntry()); + + vm.render(testEntries); + + assertTrue(vm.latestContext.integers().containsKey("longest.label.width")); + assertEquals("\\begin{thebibliography}{1}", vm.getStack().pop()); + } + + @Test + public void testDuplicateEmptyPopSwapIf() throws RecognitionException { + BstVM vm = new BstVM(""" + FUNCTION { emphasize } { + duplicate$ empty$ + { pop$ "" } + { "{\\em " swap$ * "}" * } + if$ + } + FUNCTION { test } { + "" emphasize + "Hello" emphasize + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("{\\em Hello}", vm.getStack().pop()); + assertEquals("", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + public void testPreambleWriteNewlineQuote() { + BstVM vm = new BstVM(""" + FUNCTION { test } { + preamble$ + write$ + newline$ + "hello" + write$ + quote$ "quoted" * quote$ * + write$ + } + EXECUTE { test } + """); + + BibDatabase testDatabase = new BibDatabase(); + testDatabase.setPreamble("A Preamble"); + + String result = vm.render(Collections.emptyList(), testDatabase); + + assertEquals("A Preamble\nhello\"quoted\"", result); + } +} diff --git a/src/test/java/org/jabref/logic/bst/BstVMTest.java b/src/test/java/org/jabref/logic/bst/BstVMTest.java new file mode 100644 index 00000000000..4a66a8d5836 --- /dev/null +++ b/src/test/java/org/jabref/logic/bst/BstVMTest.java @@ -0,0 +1,220 @@ +package org.jabref.logic.bst; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import org.antlr.v4.runtime.RecognitionException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BstVMTest { + + public static BibEntry defaultTestEntry() { + return new BibEntry(StandardEntryType.InProceedings) + .withCitationKey("canh05") + .withField(StandardField.AUTHOR, "Crowston, K. and Annabi, H. and Howison, J. and Masango, C.") + .withField(StandardField.TITLE, "Effective work practices for floss development: A model and propositions") + .withField(StandardField.BOOKTITLE, "Hawaii International Conference On System Sciences (HICSS)") + .withField(StandardField.YEAR, "2005") + .withField(StandardField.OWNER, "oezbek") + .withField(StandardField.TIMESTAMP, "2006.05.29") + .withField(StandardField.URL, "http://james.howison.name/publications.html"); + } + + @Test + public void testAbbrv() throws RecognitionException, IOException { + BstVM vm = new BstVM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); + List testEntries = List.of(defaultTestEntry()); + + String expected = "\\begin{thebibliography}{1}\\bibitem{canh05}K.~Crowston, H.~Annabi, J.~Howison, and C.~Masango.\\newblock Effective work practices for floss development: A model and propositions.\\newblock In {\\em Hawaii International Conference On System Sciences (HICSS)}, 2005.\\end{thebibliography}"; + String result = vm.render(testEntries); + + assertEquals( + expected.replaceAll("\\s", ""), + result.replaceAll("\\s", "")); + } + + @Test + public void testSimple() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { address author title type } { } { label } + INTEGERS { output.state before.all mid.sentence after.sentence after.block } + FUNCTION { init.state.consts }{ + #0 'before.all := + #1 'mid.sentence := + #2 'after.sentence := + #3 'after.block := + } + STRINGS { s t } + READ + """); + List testEntries = List.of(defaultTestEntry()); + + vm.render(testEntries); + + assertEquals(2, vm.latestContext.strings().size()); + assertEquals(7, vm.latestContext.integers().size()); + assertEquals(1, vm.latestContext.entries().size()); + assertEquals(5, vm.latestContext.entries().get(0).fields.size()); + assertEquals(38, vm.latestContext.functions().size()); + } + + @Test + public void testLabel() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { title } {} { label } + FUNCTION { test } { + label #0 = + title 'label := + #5 label #6 pop$ } + READ + ITERATE { test } + """); + List testEntries = List.of(defaultTestEntry()); + + vm.render(testEntries); + + assertEquals( + "Effective work practices for floss development: A model and propositions", + vm.latestContext.stack().pop()); + } + + @Test + public void testQuote() throws RecognitionException { + BstVM vm = new BstVM("FUNCTION { a }{ quote$ quote$ * } EXECUTE { a }"); + + vm.render(Collections.emptyList()); + assertEquals("\"\"", vm.latestContext.stack().pop()); + } + + @Test + public void testBuildIn() throws RecognitionException { + BstVM vm = new BstVM("EXECUTE { global.max$ }"); + + vm.render(Collections.emptyList()); + + assertEquals(Integer.MAX_VALUE, vm.latestContext.stack().pop()); + assertTrue(vm.latestContext.stack().empty()); + } + + @Test + public void testVariables() throws RecognitionException { + BstVM vm = new BstVM(""" + STRINGS { t } + FUNCTION { not } { + { #0 } { #1 } if$ + } + FUNCTION { n.dashify } { + "HELLO-WORLD" 't := + t empty$ not + } + EXECUTE { n.dashify } + """); + + vm.render(Collections.emptyList()); + + assertEquals(BstVM.TRUE, vm.latestContext.stack().pop()); + } + + @Test + public void testHypthenatedName() throws RecognitionException, IOException { + BstVM vm = new BstVM(Path.of("src/test/resources/org/jabref/logic/bst/abbrv.bst")); + List testEntries = List.of( + new BibEntry(StandardEntryType.Article) + .withCitationKey("canh05") + .withField(StandardField.AUTHOR, "Jean-Paul Sartre") + ); + + String result = vm.render(testEntries); + + assertTrue(result.contains("J.-P. Sartre")); + } + + @Test + void testAbbrevStyleChopWord() { + BstVM vm = new BstVM(""" + STRINGS { s } + INTEGERS { len } + + FUNCTION { chop.word } + { + 's := + 'len := + s #1 len substring$ = + { s len #1 + global.max$ substring$ } + 's + if$ + } + + FUNCTION { test } { + "A " #2 + "A Colorful Morning" + chop.word + + "An " #3 + "A Colorful Morning" + chop.word + } + + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals("A Colorful Morning", vm.latestContext.stack().pop()); + assertEquals("Colorful Morning", vm.latestContext.stack().pop()); + assertEquals(0, vm.latestContext.stack().size()); + } + + @Test + void testAbbrevStyleSortFormatTitle() { + BstVM vm = new BstVM(""" + STRINGS { s t } + INTEGERS { len } + FUNCTION { sortify } { + purify$ + "l" change.case$ + } + + FUNCTION { chop.word } + { + 's := + 'len := + s #1 len substring$ = + { s len #1 + global.max$ substring$ } + 's + if$ + } + + FUNCTION { sort.format.title } + { 't := + "A " #2 + "An " #3 + "The " #4 t chop.word + chop.word + chop.word + sortify + #1 global.max$ substring$ + } + + FUNCTION { test } { + "A Colorful Morning" + sort.format.title + } + + EXECUTE {test} + """); + + vm.render(Collections.emptyList()); + + assertEquals("colorful morning", vm.latestContext.stack().pop()); + } +} diff --git a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java new file mode 100644 index 00000000000..366ba0a9d55 --- /dev/null +++ b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java @@ -0,0 +1,249 @@ +package org.jabref.logic.bst; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.types.StandardEntryType; + +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.tree.ParseTree; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BstVMVisitorTest { + + @Test + public void testVisitStringsCommand() { + BstVM vm = new BstVM("STRINGS { test.string1 test.string2 test.string3 }"); + + vm.render(Collections.emptyList()); + + Map strList = vm.latestContext.strings(); + assertTrue(strList.containsKey("test.string1")); + assertNull(strList.get("test.string1")); + assertTrue(strList.containsKey("test.string2")); + assertNull(strList.get("test.string2")); + assertTrue(strList.containsKey("test.string3")); + assertNull(strList.get("test.string3")); + } + + @Test + public void testVisitIntegersCommand() { + BstVM vm = new BstVM("INTEGERS { variable.a variable.b variable.c }"); + + vm.render(Collections.emptyList()); + + Map integersList = vm.latestContext.integers(); + assertTrue(integersList.containsKey("variable.a")); + assertEquals(0, integersList.get("variable.a")); + assertTrue(integersList.containsKey("variable.b")); + assertEquals(0, integersList.get("variable.b")); + assertTrue(integersList.containsKey("variable.c")); + assertEquals(0, integersList.get("variable.c")); + } + + @Test + void testVisitFunctionCommand() { + BstVM vm = new BstVM(""" + FUNCTION { test.func } { #1 'test.var := } + EXECUTE { test.func } + """); + + vm.render(Collections.emptyList()); + + Map functions = vm.latestContext.functions(); + assertTrue(functions.containsKey("test.func")); + assertNotNull(functions.get("test.func")); + } + + @Test + void testVisitMacroCommand() { + BstVM vm = new BstVM(""" + MACRO { jan } { "January" } + EXECUTE { jan } + """); + + vm.render(Collections.emptyList()); + + Map functions = vm.latestContext.functions(); + assertTrue(functions.containsKey("jan")); + assertNotNull(functions.get("jan")); + assertEquals("January", vm.latestContext.stack().pop()); + assertTrue(vm.latestContext.stack().isEmpty()); + } + + @Test + void testVisitEntryCommand() { + BstVM vm = new BstVM("ENTRY { address author title type } { variable } { label }"); + List testEntries = List.of(BstVMTest.defaultTestEntry()); + + vm.render(testEntries); + + BstEntry bstEntry = vm.latestContext.entries().get(0); + assertTrue(bstEntry.fields.containsKey("address")); + assertTrue(bstEntry.fields.containsKey("author")); + assertTrue(bstEntry.fields.containsKey("title")); + assertTrue(bstEntry.fields.containsKey("type")); + assertTrue(bstEntry.localIntegers.containsKey("variable")); + assertTrue(bstEntry.localStrings.containsKey("label")); + assertTrue(bstEntry.localStrings.containsKey("sort.key$")); + } + + @Test + void testVisitReadCommand() { + BstVM vm = new BstVM(""" + ENTRY { author title booktitle year owner timestamp url } { } { } + READ + """); + List testEntries = List.of(BstVMTest.defaultTestEntry()); + + vm.render(testEntries); + + Map fields = vm.latestContext.entries().get(0).fields; + assertEquals("Crowston, K. and Annabi, H. and Howison, J. and Masango, C.", fields.get("author")); + assertEquals("Effective work practices for floss development: A model and propositions", fields.get("title")); + assertEquals("Hawaii International Conference On System Sciences (HICSS)", fields.get("booktitle")); + assertEquals("2005", fields.get("year")); + assertEquals("oezbek", fields.get("owner")); + assertEquals("2006.05.29", fields.get("timestamp")); + assertEquals("http://james.howison.name/publications.html", fields.get("url")); + } + + @Test + public void testVisitExecuteCommand() throws RecognitionException { + BstVM vm = new BstVM(""" + INTEGERS { variable.a } + FUNCTION { init.state.consts } { #5 'variable.a := } + EXECUTE { init.state.consts } + """); + + vm.render(Collections.emptyList()); + + assertEquals(5, vm.latestContext.integers().get("variable.a")); + } + + @Test + public void testVisitIterateCommand() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { } { } { } + FUNCTION { test } { cite$ } + READ + ITERATE { test } + """); + List testEntries = List.of( + BstVMTest.defaultTestEntry(), + new BibEntry(StandardEntryType.Article) + .withCitationKey("test")); + + vm.render(testEntries); + + assertEquals(2, vm.getStack().size()); + assertEquals("test", vm.getStack().pop()); + assertEquals("canh05", vm.getStack().pop()); + } + + @Test + public void testVisitReverseCommand() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { } { } { } + FUNCTION { test } { cite$ } + READ + REVERSE { test } + """); + List testEntries = List.of( + BstVMTest.defaultTestEntry(), + new BibEntry(StandardEntryType.Article) + .withCitationKey("test")); + + vm.render(testEntries); + + assertEquals(2, vm.getStack().size()); + assertEquals("canh05", vm.getStack().pop()); + assertEquals("test", vm.getStack().pop()); + } + + @Test + public void testVisitSortCommand() throws RecognitionException { + BstVM vm = new BstVM(""" + ENTRY { } { } { } + FUNCTION { presort } { cite$ 'sort.key$ := } + ITERATE { presort } + SORT + """); + List testEntries = List.of( + new BibEntry(StandardEntryType.Article).withCitationKey("c"), + new BibEntry(StandardEntryType.Article).withCitationKey("b"), + new BibEntry(StandardEntryType.Article).withCitationKey("d"), + new BibEntry(StandardEntryType.Article).withCitationKey("a")); + + vm.render(testEntries); + + List sortedEntries = vm.latestContext.entries(); + assertEquals(Optional.of("a"), sortedEntries.get(0).entry.getCitationKey()); + assertEquals(Optional.of("b"), sortedEntries.get(1).entry.getCitationKey()); + assertEquals(Optional.of("c"), sortedEntries.get(2).entry.getCitationKey()); + assertEquals(Optional.of("d"), sortedEntries.get(3).entry.getCitationKey()); + } + + @Test + void testVisitIdentifier() { + BstVM vm = new BstVM(""" + ENTRY { } { local.variable } { local.label } + READ + STRINGS { label } + INTEGERS { variable } + FUNCTION { test } { + #1 'local.variable := + #2 'variable := + "TEST" 'local.label := + "TEST-GLOBAL" 'label := + local.label local.variable + label variable + } + ITERATE { test } + """); + List testEntries = List.of(BstVMTest.defaultTestEntry()); + + vm.render(testEntries); + + assertEquals(2, vm.getStack().pop()); + assertEquals("TEST-GLOBAL", vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals("TEST", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + @Test + void testVisitStackitem() { + BstVM vm = new BstVM(""" + STRINGS { t } + FUNCTION { test2 } { #3 } + FUNCTION { test } { + "HELLO" + #1 + 't + { #2 } + test2 + } + EXECUTE { test } + """); + + vm.render(Collections.emptyList()); + + assertEquals(3, vm.getStack().pop()); + assertTrue(vm.getStack().pop() instanceof ParseTree); + assertEquals(new BstVMVisitor.Identifier("t"), vm.getStack().pop()); + assertEquals(1, vm.getStack().pop()); + assertEquals("HELLO", vm.getStack().pop()); + assertEquals(0, vm.getStack().size()); + } + + // stackitem +} diff --git a/src/test/java/org/jabref/logic/bst/TestVM.java b/src/test/java/org/jabref/logic/bst/TestVM.java deleted file mode 100644 index bb9fc8ad193..00000000000 --- a/src/test/java/org/jabref/logic/bst/TestVM.java +++ /dev/null @@ -1,634 +0,0 @@ -package org.jabref.logic.bst; - -import java.io.File; -import java.io.IOException; -import java.io.StringReader; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.jabref.logic.bst.VM.BstEntry; -import org.jabref.logic.bst.VM.StackFunction; -import org.jabref.logic.importer.ImportFormatPreferences; -import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.importer.fileformat.BibtexParser; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.util.DummyFileUpdateMonitor; - -import org.antlr.runtime.RecognitionException; -import org.junit.jupiter.api.Test; -import org.mockito.Answers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; - -public class TestVM { - - @Test - public void testAbbrv() throws RecognitionException, IOException { - VM vm = new VM(new File("src/test/resources/org/jabref/logic/bst/abbrv.bst")); - List v = List.of(t1BibtexEntry()); - - String expected = "\\begin{thebibliography}{1}\\bibitem{canh05}K.~Crowston, H.~Annabi, J.~Howison, and C.~Masango.\\newblock Effective work practices for floss development: A model and propositions.\\newblock In {\\em Hawaii International Conference On System Sciences (HICSS)}, 2005.\\end{thebibliography}"; - - assertEquals(expected.replaceAll("\\s", ""), vm.run(v).replaceAll("\\s", "")); - } - - @Test - public void testVMSimple() throws RecognitionException, IOException { - - VM vm = new VM("ENTRY { " + " address " + " author " + " title " + " type " - + "} {} { label }" + "INTEGERS { output.state before.all" - + " mid.sentence after.sentence after.block }" - + "FUNCTION {init.state.consts}{ #0 'before.all := " - + " #1 'mid.sentence := #2 'after.sentence := #3 'after.block := } " - + "STRINGS { s t } " + "READ"); - - List v = List.of(t1BibtexEntry()); - - vm.run(v); - - assertEquals(2, vm.getStrings().size()); - assertEquals(7, vm.getIntegers().size()); - assertEquals(1, vm.getEntries().size()); - assertEquals(5, vm.getEntries().get(0).fields.size()); - assertEquals(38, vm.getFunctions().size()); - } - - @Test - public void testLabel() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { title } {} { label } " - + "FUNCTION { test } { label #0 = title 'label := #5 label #6 pop$ } " + "READ " - + "ITERATE { test }"); - - List v = List.of(t1BibtexEntry()); - - vm.run(v); - - assertEquals("Effective work practices for floss development: A model and propositions", vm - .getStack() - .pop()); - } - - @Test - public void testQuote() throws RecognitionException { - VM vm = new VM("FUNCTION {a}{ quote$ quote$ * } EXECUTE {a}"); - - vm.run(Collections.emptyList()); - assertEquals("\"\"", vm.getStack().pop()); - } - - @Test - public void testVMFunction1() throws RecognitionException { - VM vm = new VM("FUNCTION {init.state.consts}{ #0 'before.all := } "); - - vm.run(Collections.emptyList()); - - assertEquals(38, vm.getFunctions().size()); - - assertTrue(vm.getFunctions().get("init.state.consts") instanceof StackFunction); - - StackFunction fun = (StackFunction) vm.getFunctions().get("init.state.consts"); - assertEquals(3, fun.getTree().getChildCount()); - } - - @Test - public void testVMExecuteSimple() throws RecognitionException { - VM vm = new VM("INTEGERS { variable.a } " + "FUNCTION {init.state.consts}{ #5 'variable.a := } " - + "EXECUTE {init.state.consts}"); - - vm.run(Collections.emptyList()); - - assertEquals(Integer.valueOf(5), vm.getIntegers().get("variable.a")); - } - - @Test - public void testVMExecuteSimple2() throws RecognitionException { - VM vm = new VM("FUNCTION {a}{ #5 #5 = " + "#1 #2 = " + "#3 #4 < " + "#4 #3 < " - + "#4 #4 < " + "#3 #4 > " + "#4 #3 > " + "#4 #4 > " + "\"H\" \"H\" = " - + "\"H\" \"Ha\" = } " + "EXECUTE {a}"); - - vm.run(Collections.emptyList()); - - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testVMIfSkipPop() throws RecognitionException { - VM vm = new VM("FUNCTION {not} { { #0 } { #1 } if$ }" - + "FUNCTION {and} { 'skip$ { pop$ #0 } if$ }" - + "FUNCTION {or} { { pop$ #1 } 'skip$ if$ }" + "FUNCTION {test} { " - + "#1 #1 and #0 #1 and #1 #0 and #0 #0 and " + "#0 not #1 not " - + "#1 #1 or #0 #1 or #1 #0 or #0 #0 or }" + "EXECUTE {test}"); - - vm.run(Collections.emptyList()); - - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testVMArithmetic() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { " + "#1 #1 + #5 #2 - }" + "EXECUTE {test}"); - - vm.run(Collections.emptyList()); - - assertEquals(3, vm.getStack().pop()); - assertEquals(2, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testVMArithmetic2() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { " + "#1 \"HELLO\" + #5 #2 - }" + "EXECUTE {test}"); - assertThrows(VMException.class, () -> vm.run(Collections.emptyList())); - } - - @Test - public void testNumNames() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { \"Johnny Foo and Mary Bar\" num.names$ }" + "EXECUTE {test}"); - - vm.run(Collections.emptyList()); - - assertEquals(2, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testNumNames2() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { \"Johnny Foo { and } Mary Bar\" num.names$ }" - + "EXECUTE {test}"); - - vm.run(Collections.emptyList()); - - assertEquals(1, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testVMStringOps1() throws RecognitionException { - VM vm = new VM( - "FUNCTION {test} { \"H\" \"allo\" * \"Johnny\" add.period$ \"Johnny.\" add.period$" - + "\"Johnny!\" add.period$ \"Johnny?\" add.period$ \"Johnny} }}}\" add.period$" - + "\"Johnny!}\" add.period$ \"Johnny?}\" add.period$ \"Johnny.}\" add.period$ }" - + "EXECUTE {test}"); - - vm.run(Collections.emptyList()); - - assertEquals("Johnny.}", vm.getStack().pop()); - assertEquals("Johnny?}", vm.getStack().pop()); - assertEquals("Johnny!}", vm.getStack().pop()); - assertEquals("Johnny.}", vm.getStack().pop()); - assertEquals("Johnny?", vm.getStack().pop()); - assertEquals("Johnny!", vm.getStack().pop()); - assertEquals("Johnny.", vm.getStack().pop()); - assertEquals("Johnny.", vm.getStack().pop()); - assertEquals("Hallo", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testSubstring() throws RecognitionException { - VM vm = new VM("FUNCTION {test} " + "{ \"123456789\" #2 #1 substring$ " + // 2 - " \"123456789\" #4 global.max$ substring$ " + // 456789 - " \"123456789\" #1 #9 substring$ " + // 123456789 - " \"123456789\" #1 #10 substring$ " + // 123456789 - " \"123456789\" #1 #99 substring$ " + // 123456789 - - " \"123456789\" #-7 #3 substring$ " + // 123 - " \"123456789\" #-1 #1 substring$ " + // 9 - " \"123456789\" #-1 #3 substring$ " + // 789 - " \"123456789\" #-2 #2 substring$ " + // 78 - - "} EXECUTE {test} "); - - vm.run(Collections.emptyList()); - - assertEquals("78", vm.getStack().pop()); - assertEquals("789", vm.getStack().pop()); - assertEquals("9", vm.getStack().pop()); - assertEquals("123", vm.getStack().pop()); - - assertEquals("123456789", vm.getStack().pop()); - assertEquals("123456789", vm.getStack().pop()); - assertEquals("123456789", vm.getStack().pop()); - assertEquals("456789", vm.getStack().pop()); - assertEquals("2", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testEmpty() throws RecognitionException, IOException { - VM vm = new VM("ENTRY {title}{}{} READ STRINGS { s } FUNCTION {test} " + "{ s empty$ " + // FALSE - "\"\" empty$ " + // FALSE - "\" \" empty$ " + // FALSE - " title empty$ " + // FALSE - " \" HALLO \" empty$ } ITERATE {test} "); - - List v = List.of(TestVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}")); - vm.run(v); - assertEquals(VM.FALSE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(VM.TRUE, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testDuplicateEmptyPopSwapIf() throws RecognitionException { - VM vm = new VM("FUNCTION {emphasize} " + "{ duplicate$ empty$ " + " { pop$ \"\" } " - + " { \"{\\em \" swap$ * \"}\" * } " + " if$ " + "} " + "FUNCTION {test} {" - + " \"\" emphasize " + " \"Hello\" emphasize " + "}" + "EXECUTE {test} "); - - vm.run(Collections.emptyList()); - - assertEquals("{\\em Hello}", vm.getStack().pop()); - assertEquals("", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testChangeCase() throws RecognitionException { - VM vm = new VM( - "STRINGS { title } " - + "READ " - + "FUNCTION {format.title}" - + " { duplicate$ empty$ " - + " { pop$ \"\" } " - + " { \"t\" change.case$ } " - + " if$ " - + "} " - + "FUNCTION {test} {" - + " \"hello world\" \"u\" change.case$ format.title " - + " \"Hello World\" format.title " - + " \"\" format.title " - + " \"{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase\" \"u\" change.case$ format.title " - + "}" + "EXECUTE {test} "); - - vm.run(Collections.emptyList()); - - assertEquals( - "{A}{D}/{C}ycle: {I}{B}{M}'s {F}ramework for {A}pplication {D}evelopment and {C}ase", - vm.getStack().pop()); - assertEquals("", vm.getStack().pop()); - assertEquals("Hello world", vm.getStack().pop()); - assertEquals("Hello world", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testTextLength() throws RecognitionException { - VM vm = new VM("FUNCTION {test} {" + " \"hello world\" text.length$ " - + " \"Hello {W}orld\" text.length$ " + " \"\" text.length$ " - + " \"{A}{D}/{Cycle}\" text.length$ " - + " \"{\\This is one character}\" text.length$ " - + " \"{\\This {is} {one} {c{h}}aracter as well}\" text.length$ " - + " \"{\\And this too\" text.length$ " + " \"These are {\\11}\" text.length$ " + "} " - + "EXECUTE {test} "); - - vm.run(Collections.emptyList()); - - assertEquals(11, vm.getStack().pop()); - assertEquals(1, vm.getStack().pop()); - assertEquals(1, vm.getStack().pop()); - assertEquals(1, vm.getStack().pop()); - assertEquals(8, vm.getStack().pop()); - assertEquals(0, vm.getStack().pop()); - assertEquals(11, vm.getStack().pop()); - assertEquals(11, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testVMIntToStr() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { #3 int.to.str$ #9999 int.to.str$}" + "EXECUTE {test}"); - - vm.run(Collections.emptyList()); - - assertEquals("9999", vm.getStack().pop()); - assertEquals("3", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testVMChrToInt() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { \"H\" chr.to.int$ }" + "EXECUTE {test}"); - - vm.run(Collections.emptyList()); - - assertEquals(72, vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testVMChrToIntIntToChr() throws RecognitionException { - VM vm = new VM("FUNCTION {test} { \"H\" chr.to.int$ int.to.chr$ }" + "EXECUTE {test}"); - - vm.run(Collections.emptyList()); - - assertEquals("H", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testSort() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { title } { } { label }" - + "FUNCTION {presort} { cite$ 'sort.key$ := } ITERATE { presort } SORT"); - - List v = List.of( - TestVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}"), - TestVM.bibtexString2BibtexEntry("@article{b, author=\"BBB\"}"), - TestVM.bibtexString2BibtexEntry("@article{d, author=\"DDD\"}"), - TestVM.bibtexString2BibtexEntry("@article{c, author=\"CCC\"}")); - vm.run(v); - - List v2 = vm.getEntries(); - assertEquals(Optional.of("a"), v2.get(0).entry.getCitationKey()); - assertEquals(Optional.of("b"), v2.get(1).entry.getCitationKey()); - assertEquals(Optional.of("c"), v2.get(2).entry.getCitationKey()); - assertEquals(Optional.of("d"), v2.get(3).entry.getCitationKey()); - } - - @Test - public void testBuildIn() throws RecognitionException { - VM vm = new VM("EXECUTE {global.max$}"); - - vm.run(Collections.emptyList()); - - assertEquals(Integer.MAX_VALUE, vm.getStack().pop()); - assertTrue(vm.getStack().empty()); - } - - @Test - public void testVariables() throws RecognitionException { - VM vm = new VM(" STRINGS { t } " - + " FUNCTION {not} { { #0 } { #1 } if$ } " - + " FUNCTION {n.dashify} { \"HELLO-WORLD\" 't := t empty$ not } " - + " EXECUTE {n.dashify} "); - - vm.run(Collections.emptyList()); - - assertEquals(VM.TRUE, vm.getStack().pop()); - } - - @Test - public void testWhile() throws RecognitionException { - VM vm = new VM( - "STRINGS { t } " - + "FUNCTION {not} { " - + " { #0 } { #1 } if$ } " - + "FUNCTION {n.dashify} " - + "{ \"HELLO-WORLD\" " - + " 't := " - + " \"\" " - + " { t empty$ not } " - + " { t #1 #1 substring$ \"-\" = " - + " { t #1 #2 substring$ \"--\" = not " - + " { \"--\" * " - + " t #2 global.max$ substring$ 't := " - + " } " - + " { { t #1 #1 substring$ \"-\" = } " - + " { \"-\" * " - + " t #2 global.max$ substring$ 't := " - + " } " - + " while$ " - + " } " - + " if$ " - + " } " - + " { t #1 #1 substring$ * " - + " t #2 global.max$ substring$ 't := " - + " } " - + " if$ " - + " } " - + " while$ " - + " } " - + " EXECUTE {n.dashify} "); - - List v = Collections.emptyList(); - vm.run(v); - - assertEquals(1, vm.getStack().size()); - assertEquals("HELLO--WORLD", vm.getStack().pop()); - } - - @Test - public void testType() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { title } { } { label }" - + "FUNCTION {presort} { cite$ 'sort.key$ := } ITERATE { presort } SORT FUNCTION {test} { type$ } ITERATE { test }"); - - List v = List.of( - TestVM.bibtexString2BibtexEntry("@article{a, author=\"AAA\"}"), - TestVM.bibtexString2BibtexEntry("@book{b, author=\"BBB\"}"), - TestVM.bibtexString2BibtexEntry("@misc{c, author=\"CCC\"}"), - TestVM.bibtexString2BibtexEntry("@inproceedings{d, author=\"DDD\"}")); - vm.run(v); - - assertEquals(4, vm.getStack().size()); - assertEquals("inproceedings", vm.getStack().pop()); - assertEquals("misc", vm.getStack().pop()); - assertEquals("book", vm.getStack().pop()); - assertEquals("article", vm.getStack().pop()); - } - - @Test - public void testMissing() throws RecognitionException, IOException { - VM vm = new VM( - "ENTRY { title } { } { label } " + - "FUNCTION {presort} { cite$ 'sort.key$ := } " + - "ITERATE {presort} " + - "READ SORT " + - "FUNCTION {test}{ title missing$ cite$ } " + - "ITERATE { test }"); - - List v = List.of( - t1BibtexEntry(), - TestVM.bibtexString2BibtexEntry("@article{test, author=\"No title\"}")); - vm.run(v); - - assertEquals(4, vm.getStack().size()); - - assertEquals("test", vm.getStack().pop()); - assertEquals(1, vm.getStack().pop()); - assertEquals("canh05", vm.getStack().pop()); - assertEquals(0, vm.getStack().pop()); - } - - @Test - public void testFormatName() throws RecognitionException { - VM vm = new VM( - "FUNCTION {format}{ \"Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin\" #1 \"{vv~}{ll}{, jj}{, f}?\" format.name$ }" - + "EXECUTE {format}"); - - List v = Collections.emptyList(); - vm.run(v); - assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testFormatName2() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { author } { } { label } " + "FUNCTION {presort} { cite$ 'sort.key$ := } " - + "ITERATE { presort } " + "READ " + "SORT " - + "FUNCTION {format}{ author #2 \"{vv~}{ll}{, jj}{, f}?\" format.name$ }" + "ITERATE {format}"); - - List v = List.of( - t1BibtexEntry(), - TestVM.bibtexString2BibtexEntry( - "@book{test, author=\"Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin\"}")); - vm.run(v); - assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", vm.getStack().pop()); - assertEquals("Annabi, H?", vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testCallType() throws RecognitionException, IOException { - VM vm = new VM( - "ENTRY { title } { } { label } FUNCTION {presort} { cite$ 'sort.key$ := } ITERATE { presort } READ SORT " - + "FUNCTION {inproceedings}{ \"InProceedings called on \" title * } " - + "FUNCTION {book}{ \"Book called on \" title * } " + " ITERATE { call.type$ }"); - - List v = List.of( - t1BibtexEntry(), - TestVM.bibtexString2BibtexEntry("@book{test, title=\"Test\"}")); - - vm.run(v); - - assertEquals(2, vm.getStack().size()); - - assertEquals("Book called on Test", vm.getStack().pop()); - assertEquals( - "InProceedings called on Effective work practices for floss development: A model and propositions", - vm.getStack().pop()); - assertEquals(0, vm.getStack().size()); - } - - @Test - public void testIterate() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { " + " address " + " author " + " title " + " type " - + "} {} { label } " + "FUNCTION {test}{ cite$ } " + "READ " + "ITERATE { test }"); - - List v = List.of( - t1BibtexEntry(), - TestVM.bibtexString2BibtexEntry("@article{test, title=\"BLA\"}")); - - vm.run(v); - - assertEquals(2, vm.getStack().size()); - - String s1 = (String) vm.getStack().pop(); - String s2 = (String) vm.getStack().pop(); - - if ("canh05".equals(s1)) { - assertEquals("test", s2); - } else { - assertEquals("canh05", s2); - assertEquals("test", s1); - } - } - - @Test - public void testWidth() throws RecognitionException, IOException { - VM vm = new VM("ENTRY { " + " address " + " author " + " title " + " type " - + "} {} { label } " + - "STRINGS { longest.label } " + - "INTEGERS { number.label longest.label.width } " + - "FUNCTION {initialize.longest.label} " + - "{ \"\" 'longest.label := " + - " #1 'number.label := " + - " #0 'longest.label.width := " + - "} " + - " " + - " FUNCTION {longest.label.pass} " + - " { number.label int.to.str$ 'label := " + - " number.label #1 + 'number.label := " + - " label width$ longest.label.width > " + - " { label 'longest.label := " + - " label width$ 'longest.label.width := " + - " } " + - " 'skip$ " + - " if$ " + - " } " + - " " + - " EXECUTE {initialize.longest.label} " + - " " + - " ITERATE {longest.label.pass} " + - "FUNCTION {begin.bib} " + - "{ preamble$ empty$" + - " 'skip$" + - " { preamble$ write$ newline$ }" + - " if$" + - " \"\\begin{thebibliography}{\" longest.label * \"}\" *" + - "}" + - "EXECUTE {begin.bib}"); - - List v = List.of(t1BibtexEntry()); - - vm.run(v); - - assertTrue(vm.getIntegers().containsKey("longest.label.width")); - assertEquals("\\begin{thebibliography}{1}", vm.getStack().pop()); - } - - @Test - public void testVMSwap() throws RecognitionException { - VM vm = new VM("FUNCTION {a}{ #3 \"Hallo\" swap$ } EXECUTE { a }"); - - List v = Collections.emptyList(); - vm.run(v); - - assertEquals(2, vm.getStack().size()); - assertEquals(3, vm.getStack().pop()); - assertEquals("Hallo", vm.getStack().pop()); - } - - @Test - public void testHypthenatedName() throws RecognitionException, IOException { - VM vm = new VM(new File("src/test/resources/org/jabref/logic/bst/abbrv.bst")); - List v = List.of(TestVM.bibtexString2BibtexEntry("@article{canh05, author = \"Jean-Paul Sartre\" }")); - assertTrue(vm.run(v).contains("J.-P. Sartre")); - } - - private static BibEntry bibtexString2BibtexEntry(String s) throws IOException { - ParserResult result = new BibtexParser(mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS), new DummyFileUpdateMonitor()).parse(new StringReader(s)); - Collection c = result.getDatabase().getEntries(); - assertEquals(1, c.size()); - return c.iterator().next(); - } - - private static String t1BibtexString() { - return "@inproceedings{canh05,\n" - + " author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.},\n" - + " title = {Effective work practices for floss development: A model and propositions},\n" - + " booktitle = {Hawaii International Conference On System Sciences (HICSS)},\n" - + " year = {2005},\n" + " owner = {oezbek},\n" + " timestamp = {2006.05.29},\n" - + " url = {http://james.howison.name/publications.html}}\n"; - } - - private static BibEntry t1BibtexEntry() throws IOException { - return TestVM.bibtexString2BibtexEntry(t1BibtexString()); - } -} diff --git a/src/test/java/org/jabref/logic/bst/BibtexCaseChangersTest.java b/src/test/java/org/jabref/logic/bst/util/BstCaseChangersTest.java similarity index 93% rename from src/test/java/org/jabref/logic/bst/BibtexCaseChangersTest.java rename to src/test/java/org/jabref/logic/bst/util/BstCaseChangersTest.java index 5349be3ef3f..3bf615093e4 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexCaseChangersTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstCaseChangersTest.java @@ -1,8 +1,8 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import java.util.stream.Stream; -import org.jabref.logic.bst.BibtexCaseChanger.FORMAT_MODE; +import org.jabref.logic.bst.util.BstCaseChanger.FormatMode; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -12,12 +12,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class BibtexCaseChangersTest { +public class BstCaseChangersTest { @ParameterizedTest @MethodSource("provideStringsForTitleLowers") public void testChangeCaseTitleLowers(String expected, String toBeFormatted) { - assertEquals(expected, BibtexCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.TITLE_LOWERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FormatMode.TITLE_LOWERS)); } private static Stream provideStringsForTitleLowers() { @@ -59,7 +59,7 @@ private static Stream provideStringsForTitleLowers() { @ParameterizedTest @MethodSource("provideStringsForAllLowers") public void testChangeCaseAllLowers(String expected, String toBeFormatted) { - assertEquals(expected, BibtexCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_LOWERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FormatMode.ALL_LOWERS)); } private static Stream provideStringsForAllLowers() { @@ -90,7 +90,7 @@ private static Stream provideStringsForAllLowers() { @ParameterizedTest @MethodSource("provideStringsForAllUppers") public void testChangeCaseAllUppers(String expected, String toBeFormatted) { - assertEquals(expected, BibtexCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_UPPERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FormatMode.ALL_UPPERS)); } private static Stream provideStringsForAllUppers() { @@ -121,7 +121,7 @@ private static Stream provideStringsForAllUppers() { @ParameterizedTest @MethodSource("provideTitleCaseAllLowers") public void testTitleCaseAllLowers(String expected, String toBeFormatted) { - assertEquals(expected, BibtexCaseChanger.changeCase(toBeFormatted, FORMAT_MODE.ALL_LOWERS)); + assertEquals(expected, BstCaseChanger.changeCase(toBeFormatted, FormatMode.ALL_LOWERS)); } private static Stream provideTitleCaseAllLowers() { diff --git a/src/test/java/org/jabref/logic/bst/BibtexNameFormatterTest.java b/src/test/java/org/jabref/logic/bst/util/BstNameFormatterTest.java similarity index 52% rename from src/test/java/org/jabref/logic/bst/BibtexNameFormatterTest.java rename to src/test/java/org/jabref/logic/bst/util/BstNameFormatterTest.java index a8000752edb..4912af099bf 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexNameFormatterTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstNameFormatterTest.java @@ -1,48 +1,42 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import org.jabref.model.entry.AuthorList; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -public class BibtexNameFormatterTest { +public class BstNameFormatterTest { @Test public void testUmlautsFullNames() { - AuthorList al = AuthorList - .parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); + AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); - assertEquals("de~laVall{\\'e}e~PoussinCharles Louis Xavier~Joseph", BibtexNameFormatter.formatName(al - .getAuthor(0), "{vv}{ll}{jj}{ff}", Assertions::fail)); + assertEquals("de~laVall{\\'e}e~PoussinCharles Louis Xavier~Joseph", + BstNameFormatter.formatName(list.getAuthor(0), "{vv}{ll}{jj}{ff}")); } @Test public void testUmlautsAbbreviations() { - AuthorList al = AuthorList - .parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); + AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); - assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J.", BibtexNameFormatter.formatName(al - .getAuthor(0), "{vv~}{ll}{, jj}{, f.}", Assertions::fail)); + assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J.", + BstNameFormatter.formatName(list.getAuthor(0), "{vv~}{ll}{, jj}{, f.}")); } @Test public void testUmlautsAbbreviationsWithQuestionMark() { - AuthorList al = AuthorList - .parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); + AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); - assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", BibtexNameFormatter.formatName(al - .getAuthor(0), "{vv~}{ll}{, jj}{, f}?", Assertions::fail)); + assertEquals("de~la Vall{\\'e}e~Poussin, C.~L. X.~J?", + BstNameFormatter.formatName(list.getAuthor(0), "{vv~}{ll}{, jj}{, f}?")); } @Test public void testFormatName() { - AuthorList al = AuthorList - .parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); + AuthorList list = AuthorList.parse("Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); - assertEquals("dlVP", BibtexNameFormatter.formatName(al.getAuthor(0), "{v{}}{l{}}", - Assertions::fail)); + assertEquals("dlVP", BstNameFormatter.formatName(list.getAuthor(0), "{v{}}{l{}}")); assertNameFormatA("Meyer, J?", "Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); assertNameFormatB("J.~Meyer", "Jonathan Meyer and Charles Louis Xavier Joseph de la Vall{\\'e}e Poussin"); @@ -60,8 +54,7 @@ public void testFormatName() { } private void assertNameFormat(String string, String string2, int which, String format) { - assertEquals(string, BibtexNameFormatter.formatName(string2, which, format, - Assertions::fail)); + assertEquals(string, BstNameFormatter.formatName(string2, which, format)); } private void assertNameFormatC(String string, String string2) { @@ -79,44 +72,41 @@ private void assertNameFormatA(String string, String string2) { @Test public void matchingBraceConsumedForCompleteWords() { StringBuilder sb = new StringBuilder(); - assertEquals(6, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HELLO} {WORLD}" - .toCharArray(), 0)); + assertEquals(6, BstNameFormatter.consumeToMatchingBrace(sb, "{HELLO} {WORLD}".toCharArray(), 0)); assertEquals("{HELLO}", sb.toString()); } @Test public void matchingBraceConsumedForBracesInWords() { StringBuilder sb = new StringBuilder(); - assertEquals(18, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}" - .toCharArray(), 12)); + assertEquals(18, BstNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}".toCharArray(), 12)); assertEquals("{WORLD}", sb.toString()); } @Test public void testConsumeToMatchingBrace() { StringBuilder sb = new StringBuilder(); - assertEquals(10, BibtexNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}" - .toCharArray(), 0)); + assertEquals(10, BstNameFormatter.consumeToMatchingBrace(sb, "{HE{L{}L}O} {WORLD}".toCharArray(), 0)); assertEquals("{HE{L{}L}O}", sb.toString()); } @Test public void testGetFirstCharOfString() { - assertEquals("C", BibtexNameFormatter.getFirstCharOfString("Charles")); - assertEquals("V", BibtexNameFormatter.getFirstCharOfString("Vall{\\'e}e")); - assertEquals("{\\'e}", BibtexNameFormatter.getFirstCharOfString("{\\'e}")); - assertEquals("{\\'e", BibtexNameFormatter.getFirstCharOfString("{\\'e")); - assertEquals("E", BibtexNameFormatter.getFirstCharOfString("{E")); + assertEquals("C", BstNameFormatter.getFirstCharOfString("Charles")); + assertEquals("V", BstNameFormatter.getFirstCharOfString("Vall{\\'e}e")); + assertEquals("{\\'e}", BstNameFormatter.getFirstCharOfString("{\\'e}")); + assertEquals("{\\'e", BstNameFormatter.getFirstCharOfString("{\\'e")); + assertEquals("E", BstNameFormatter.getFirstCharOfString("{E")); } @Test public void testNumberOfChars() { - assertEquals(6, BibtexNameFormatter.numberOfChars("Vall{\\'e}e", -1)); - assertEquals(2, BibtexNameFormatter.numberOfChars("Vall{\\'e}e", 2)); - assertEquals(1, BibtexNameFormatter.numberOfChars("Vall{\\'e}e", 1)); - assertEquals(6, BibtexNameFormatter.numberOfChars("Vall{\\'e}e", 6)); - assertEquals(6, BibtexNameFormatter.numberOfChars("Vall{\\'e}e", 7)); - assertEquals(8, BibtexNameFormatter.numberOfChars("Vall{e}e", -1)); - assertEquals(6, BibtexNameFormatter.numberOfChars("Vall{\\'e this will be skipped}e", -1)); + assertEquals(6, BstNameFormatter.numberOfChars("Vall{\\'e}e", -1)); + assertEquals(2, BstNameFormatter.numberOfChars("Vall{\\'e}e", 2)); + assertEquals(1, BstNameFormatter.numberOfChars("Vall{\\'e}e", 1)); + assertEquals(6, BstNameFormatter.numberOfChars("Vall{\\'e}e", 6)); + assertEquals(6, BstNameFormatter.numberOfChars("Vall{\\'e}e", 7)); + assertEquals(8, BstNameFormatter.numberOfChars("Vall{e}e", -1)); + assertEquals(6, BstNameFormatter.numberOfChars("Vall{\\'e this will be skipped}e", -1)); } } diff --git a/src/test/java/org/jabref/logic/bst/BibtexPurifyTest.java b/src/test/java/org/jabref/logic/bst/util/BstPurifierTest.java similarity index 80% rename from src/test/java/org/jabref/logic/bst/BibtexPurifyTest.java rename to src/test/java/org/jabref/logic/bst/util/BstPurifierTest.java index 684e4366225..295b735fd37 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexPurifyTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstPurifierTest.java @@ -1,4 +1,4 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import java.util.stream.Stream; @@ -7,14 +7,13 @@ import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -public class BibtexPurifyTest { +public class BstPurifierTest { @ParameterizedTest @MethodSource("provideTestStrings") public void testPurify(String expected, String toBePurified) { - assertEquals(expected, BibtexPurify.purify(toBePurified, s -> fail("Should not Warn (" + s + ")! purify should be " + expected + " for " + toBePurified))); + assertEquals(expected, BstPurifier.purify(toBePurified)); } private static Stream provideTestStrings() { diff --git a/src/test/java/org/jabref/logic/bst/TextPrefixFunctionTest.java b/src/test/java/org/jabref/logic/bst/util/BstTextPrefixerTest.java similarity index 73% rename from src/test/java/org/jabref/logic/bst/TextPrefixFunctionTest.java rename to src/test/java/org/jabref/logic/bst/util/BstTextPrefixerTest.java index d91eadd8920..cc857101822 100644 --- a/src/test/java/org/jabref/logic/bst/TextPrefixFunctionTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstTextPrefixerTest.java @@ -1,11 +1,10 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -public class TextPrefixFunctionTest { +public class BstTextPrefixerTest { @Test public void testPrefix() { @@ -21,6 +20,6 @@ public void testPrefix() { } private static void assertPrefix(final String string, final String string2) { - assertEquals(string, BibtexTextPrefix.textPrefix(5, string2, s -> fail("Should not Warn! text.prefix$ should be " + string + " for (5) " + string2))); + assertEquals(string, BstTextPrefixer.textPrefix(5, string2)); } } diff --git a/src/test/java/org/jabref/logic/bst/BibtexWidthTest.java b/src/test/java/org/jabref/logic/bst/util/BstWidthCalculatorTest.java similarity index 91% rename from src/test/java/org/jabref/logic/bst/BibtexWidthTest.java rename to src/test/java/org/jabref/logic/bst/util/BstWidthCalculatorTest.java index 79cb00b3eb8..30f2f6770e5 100644 --- a/src/test/java/org/jabref/logic/bst/BibtexWidthTest.java +++ b/src/test/java/org/jabref/logic/bst/util/BstWidthCalculatorTest.java @@ -1,4 +1,4 @@ -package org.jabref.logic.bst; +package org.jabref.logic.bst.util; import java.util.stream.Stream; @@ -35,12 +35,12 @@ * \bibcite{canh05}{CMM{$^{+}$}05} * */ -public class BibtexWidthTest { +public class BstWidthCalculatorTest { @ParameterizedTest @MethodSource("provideTestWidth") public void testWidth(int i, String str) { - assertEquals(i, BibtexWidth.width(str)); + assertEquals(i, BstWidthCalculator.width(str)); } private static Stream provideTestWidth() { @@ -60,7 +60,7 @@ private static Stream provideTestWidth() { @ParameterizedTest @MethodSource("provideTestGetCharWidth") public void testGetCharWidth(int i, Character c) { - assertEquals(i, BibtexWidth.getCharWidth(c)); + assertEquals(i, BstWidthCalculator.getCharWidth(c)); } private static Stream provideTestGetCharWidth() { From 0d825061c2b28edd588d293f5aaf3d576aaf8305 Mon Sep 17 00:00:00 2001 From: Houssem Nasri Date: Mon, 1 Aug 2022 20:29:25 +0100 Subject: [PATCH 18/31] Improve Automatic Field Editor Dialog (#8973) --- .../java/org/jabref/gui/StateManager.java | 17 ++ ...tractAutomaticFieldEditorTabViewModel.java | 48 +++++ .../AutomaticFieldEditorAction.java | 3 +- .../AutomaticFieldEditorDialog.fxml | 3 +- .../AutomaticFieldEditorDialog.java | 65 +++++-- .../AutomaticFieldEditorViewModel.java | 23 ++- .../LastAutomaticFieldEditorEdit.java | 44 +++++ .../MoveFieldValueAction.java | 35 +++- .../NotificationPaneAdapter.java | 23 +++ .../CopyOrMoveFieldContentTab.fxml} | 20 +- .../CopyOrMoveFieldContentTabView.java | 109 +++++++++++ .../CopyOrMoveFieldContentTabViewModel.java | 183 ++++++++++++++++++ .../EditFieldContentTab.fxml} | 13 +- .../EditFieldContentTabView.java | 100 ++++++++++ .../EditFieldContentViewModel.java | 164 ++++++++++++++++ .../editfieldvalue/EditFieldValueTabView.java | 95 --------- .../EditFieldValueViewModel.java | 135 ------------- .../renamefield/RenameFieldTab.fxml | 2 +- .../renamefield/RenameFieldTabView.java | 59 +++--- .../renamefield/RenameFieldViewModel.java | 95 ++++++--- .../twofields/TwoFieldsTabView.java | 118 ----------- .../twofields/TwoFieldsViewModel.java | 141 -------------- .../jabref/model/database/BibDatabase.java | 40 +++- .../org/jabref/model/strings/StringUtil.java | 11 ++ src/main/resources/l10n/JabRef_en.properties | 20 +- ...opyOrMoveFieldContentTabViewModelTest.java | 105 ++++++++++ .../EditFieldContentTabViewModelTest.java | 118 +++++++++++ .../gui/edit/RenameFieldViewModelTest.java | 111 +++++++++++ .../jabref/model/strings/StringUtilTest.java | 24 ++- 29 files changed, 1309 insertions(+), 615 deletions(-) create mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java create mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java create mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java rename src/main/java/org/jabref/gui/edit/automaticfiededitor/{twofields/TwoFieldsTab.fxml => copyormovecontent/CopyOrMoveFieldContentTab.fxml} (71%) create mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabView.java create mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTabViewModel.java rename src/main/java/org/jabref/gui/edit/automaticfiededitor/{editfieldvalue/EditFieldValueTab.fxml => editfieldcontent/EditFieldContentTab.fxml} (85%) create mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentTabView.java create mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldcontent/EditFieldContentViewModel.java delete mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueTabView.java delete mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/editfieldvalue/EditFieldValueViewModel.java delete mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsTabView.java delete mode 100644 src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsViewModel.java create mode 100644 src/test/java/org/jabref/gui/edit/CopyOrMoveFieldContentTabViewModelTest.java create mode 100644 src/test/java/org/jabref/gui/edit/EditFieldContentTabViewModelTest.java create mode 100644 src/test/java/org/jabref/gui/edit/RenameFieldViewModelTest.java diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index a8d93152de9..cc6ab8ae8bd 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -8,9 +8,11 @@ import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyListProperty; import javafx.beans.property.ReadOnlyListWrapper; import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; @@ -18,6 +20,7 @@ import javafx.scene.Node; import javafx.util.Pair; +import org.jabref.gui.edit.automaticfiededitor.LastAutomaticFieldEditorEdit; import org.jabref.gui.sidepane.SidePaneType; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.CustomLocalDragboard; @@ -62,6 +65,8 @@ public class StateManager { private final ObservableMap dialogWindowStates = FXCollections.observableHashMap(); private final ObservableList visibleSidePanes = FXCollections.observableArrayList(); + private final ObjectProperty lastAutomaticFieldEditorEdit = new SimpleObjectProperty<>(); + public StateManager() { activeGroups.bind(Bindings.valueAt(selectedGroups, activeDatabase.orElse(null))); } @@ -172,4 +177,16 @@ public DialogWindowState getDialogWindowState(String className) { public void setDialogWindowState(String className, DialogWindowState state) { dialogWindowStates.put(className, state); } + + public ObjectProperty lastAutomaticFieldEditorEditProperty() { + return lastAutomaticFieldEditorEdit; + } + + public LastAutomaticFieldEditorEdit getLastAutomaticFieldEditorEdit() { + return lastAutomaticFieldEditorEditProperty().get(); + } + + public void setLastAutomaticFieldEditorEdit(LastAutomaticFieldEditorEdit automaticFieldEditorEdit) { + lastAutomaticFieldEditorEditProperty().set(automaticFieldEditorEdit); + } } diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java new file mode 100644 index 00000000000..fe6e60fee01 --- /dev/null +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AbstractAutomaticFieldEditorTabViewModel.java @@ -0,0 +1,48 @@ +package org.jabref.gui.edit.automaticfiededitor; + +import java.util.Collection; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.jabref.gui.AbstractViewModel; +import org.jabref.gui.StateManager; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractAutomaticFieldEditorTabViewModel extends AbstractViewModel { + public static final Logger LOGGER = LoggerFactory.getLogger(AbstractAutomaticFieldEditorTabViewModel.class); + + protected final StateManager stateManager; + + private final ObservableList allFields = FXCollections.observableArrayList(); + + public AbstractAutomaticFieldEditorTabViewModel(BibDatabase bibDatabase, StateManager stateManager) { + Objects.requireNonNull(bibDatabase); + Objects.requireNonNull(stateManager); + this.stateManager = stateManager; + + addFields(EnumSet.allOf(StandardField.class)); + addFields(bibDatabase.getAllVisibleFields()); + allFields.sort(Comparator.comparing(Field::getName)); + } + + public ObservableList getAllFields() { + return allFields; + } + + private void addFields(Collection fields) { + Set fieldsSet = new HashSet<>(allFields); + fieldsSet.addAll(fields); + allFields.setAll(fieldsSet); + } +} diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java index a56af9dfa78..904762ca02a 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorAction.java @@ -20,7 +20,6 @@ public AutomaticFieldEditorAction(StateManager stateManager, DialogService dialo @Override public void execute() { - dialogService.showCustomDialogAndWait(new AutomaticFieldEditorDialog(stateManager.getSelectedEntries(), - stateManager.getActiveDatabase().orElseThrow())); + dialogService.showCustomDialogAndWait(new AutomaticFieldEditorDialog(stateManager)); } } diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.fxml b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.fxml index f79462369a1..47d8338dca4 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.fxml +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.fxml @@ -10,6 +10,5 @@ - + diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java index 5ecbf06b275..9f6a4663d9d 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorDialog.java @@ -1,39 +1,50 @@ package org.jabref.gui.edit.automaticfiededitor; +import java.util.ArrayList; import java.util.List; +import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; import javafx.fxml.FXML; import javafx.scene.control.ButtonBar; -import javafx.scene.control.ButtonType; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import org.jabref.gui.Globals; +import org.jabref.gui.StateManager; import org.jabref.gui.util.BaseDialog; -import org.jabref.gui.util.ControlHelper; import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import com.airhacks.afterburner.views.ViewLoader; +import com.tobiasdiez.easybind.EasyBind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AutomaticFieldEditorDialog extends BaseDialog { + + private static final Logger LOGGER = LoggerFactory.getLogger(AutomaticFieldEditorDialog.class); -public class AutomaticFieldEditorDialog extends BaseDialog { - @FXML public ButtonType saveButton; - @FXML public ButtonType cancelButton; @FXML private TabPane tabPane; private final UndoManager undoManager; - private final BibDatabaseContext databaseContext; + private final BibDatabase database; private final List selectedEntries; + + private final StateManager stateManager; + private AutomaticFieldEditorViewModel viewModel; - public AutomaticFieldEditorDialog(List selectedEntries, BibDatabaseContext databaseContext) { - this.selectedEntries = selectedEntries; - this.databaseContext = databaseContext; + private List notificationPanes = new ArrayList<>(); + + public AutomaticFieldEditorDialog(StateManager stateManager) { + this.selectedEntries = stateManager.getSelectedEntries(); + this.database = stateManager.getActiveDatabase().orElseThrow().getDatabase(); + this.stateManager = stateManager; this.undoManager = Globals.undoManager; this.setTitle(Localization.lang("Automatic field editor")); @@ -42,8 +53,14 @@ public AutomaticFieldEditorDialog(List selectedEntries, BibDatabaseCon .load() .setAsDialogPane(this); - ControlHelper.setAction(saveButton, getDialogPane(), event -> saveChangesAndCloseDialog()); - ControlHelper.setAction(cancelButton, getDialogPane(), event -> cancelChangesAndCloseDialog()); + setResultConverter(buttonType -> { + if (buttonType != null && buttonType.getButtonData() == ButtonBar.ButtonData.OK_DONE) { + saveChanges(); + } else { + cancelChanges(); + } + return ""; + }); // This will prevent all dialog buttons from having the same size // Read more: https://stackoverflow.com/questions/45866249/javafx-8-alert-different-button-sizes @@ -54,20 +71,30 @@ public AutomaticFieldEditorDialog(List selectedEntries, BibDatabaseCon @FXML public void initialize() { - viewModel = new AutomaticFieldEditorViewModel(selectedEntries, databaseContext, undoManager); + viewModel = new AutomaticFieldEditorViewModel(selectedEntries, database, undoManager, stateManager); for (AutomaticFieldEditorTab tabModel : viewModel.getFieldEditorTabs()) { - tabPane.getTabs().add(new Tab(tabModel.getTabName(), tabModel.getContent())); + NotificationPaneAdapter notificationPane = new NotificationPaneAdapter(tabModel.getContent()); + notificationPanes.add(notificationPane); + tabPane.getTabs().add(new Tab(tabModel.getTabName(), notificationPane)); } + + EasyBind.listen(stateManager.lastAutomaticFieldEditorEditProperty(), (obs, old, lastEdit) -> { + viewModel.getDialogEdits().addEdit(lastEdit.getEdit()); + notificationPanes.get(lastEdit.getTabIndex()) + .notify(lastEdit.getAffectedEntries(), selectedEntries.size()); + }); } - private void saveChangesAndCloseDialog() { + private void saveChanges() { viewModel.saveChanges(); - close(); } - private void cancelChangesAndCloseDialog() { - viewModel.cancelChanges(); - close(); + private void cancelChanges() { + try { + viewModel.cancelChanges(); + } catch (CannotUndoException e) { + LOGGER.info("Could not undo", e); + } } } diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java index 0c334ddf6fa..3f517a1dabd 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/AutomaticFieldEditorViewModel.java @@ -8,11 +8,12 @@ import javafx.collections.ObservableList; import org.jabref.gui.AbstractViewModel; -import org.jabref.gui.edit.automaticfiededitor.editfieldvalue.EditFieldValueTabView; +import org.jabref.gui.StateManager; +import org.jabref.gui.edit.automaticfiededitor.copyormovecontent.CopyOrMoveFieldContentTabView; +import org.jabref.gui.edit.automaticfiededitor.editfieldcontent.EditFieldContentTabView; import org.jabref.gui.edit.automaticfiededitor.renamefield.RenameFieldTabView; -import org.jabref.gui.edit.automaticfiededitor.twofields.TwoFieldsTabView; import org.jabref.gui.undo.NamedCompound; -import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; public class AutomaticFieldEditorViewModel extends AbstractViewModel { @@ -20,17 +21,19 @@ public class AutomaticFieldEditorViewModel extends AbstractViewModel { private final ObservableList fieldEditorTabs = FXCollections.observableArrayList(); private final NamedCompound dialogEdits = new NamedCompound(NAMED_COMPOUND_EDITS); - private final BibDatabaseContext databaseContext; private final UndoManager undoManager; - public AutomaticFieldEditorViewModel(List selectedEntries, BibDatabaseContext databaseContext, UndoManager undoManager) { + public AutomaticFieldEditorViewModel(List selectedEntries, BibDatabase database, UndoManager undoManager, StateManager stateManager) { + this.undoManager = undoManager; fieldEditorTabs.addAll( - new EditFieldValueTabView(selectedEntries, databaseContext, dialogEdits), - new TwoFieldsTabView(selectedEntries, databaseContext, dialogEdits), - new RenameFieldTabView(selectedEntries, databaseContext, dialogEdits) + new EditFieldContentTabView(database, stateManager), + new CopyOrMoveFieldContentTabView(database, stateManager), + new RenameFieldTabView(database, stateManager) ); - this.databaseContext = databaseContext; - this.undoManager = undoManager; + } + + public NamedCompound getDialogEdits() { + return dialogEdits; } public ObservableList getFieldEditorTabs() { diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java new file mode 100644 index 00000000000..a1263576608 --- /dev/null +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/LastAutomaticFieldEditorEdit.java @@ -0,0 +1,44 @@ +package org.jabref.gui.edit.automaticfiededitor; + +import javax.swing.undo.AbstractUndoableEdit; +import javax.swing.undo.CannotRedoException; +import javax.swing.undo.CannotUndoException; + +import org.jabref.gui.undo.NamedCompound; + +public class LastAutomaticFieldEditorEdit extends AbstractUndoableEdit { + private final Integer affectedEntries; + private final NamedCompound edit; + + private final Integer tabIndex; + + public LastAutomaticFieldEditorEdit(Integer affectedEntries, Integer tabIndex, NamedCompound edit) { + this.affectedEntries = affectedEntries; + this.edit = edit; + this.tabIndex = tabIndex; + } + + public Integer getAffectedEntries() { + return affectedEntries; + } + + public NamedCompound getEdit() { + return edit; + } + + public Integer getTabIndex() { + return tabIndex; + } + + @Override + public void undo() throws CannotUndoException { + super.undo(); + edit.undo(); + } + + @Override + public void redo() throws CannotRedoException { + super.redo(); + edit.redo(); + } +} diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java index 8863d17de78..660c2f8454b 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/MoveFieldValueAction.java @@ -16,27 +16,48 @@ public class MoveFieldValueAction extends SimpleCommand { private final NamedCompound edits; - public MoveFieldValueAction(Field fromField, Field toField, List entries, NamedCompound edits) { + private int affectedEntriesCount; + + private final boolean overwriteToFieldContent; + + public MoveFieldValueAction(Field fromField, Field toField, List entries, NamedCompound edits, boolean overwriteToFieldContent) { this.fromField = fromField; this.toField = toField; this.entries = entries; this.edits = edits; + this.overwriteToFieldContent = overwriteToFieldContent; + } + + public MoveFieldValueAction(Field fromField, Field toField, List entries, NamedCompound edits) { + this(fromField, toField, entries, edits, true); } @Override public void execute() { + affectedEntriesCount = 0; for (BibEntry entry : entries) { String fromFieldValue = entry.getField(fromField).orElse(""); String toFieldValue = entry.getField(toField).orElse(""); - if (StringUtil.isNotBlank(fromFieldValue)) { - entry.setField(toField, fromFieldValue); - entry.setField(fromField, ""); - - edits.addEdit(new UndoableFieldChange(entry, fromField, fromFieldValue, null)); - edits.addEdit(new UndoableFieldChange(entry, toField, toFieldValue, fromFieldValue)); + if (overwriteToFieldContent || toFieldValue.isEmpty()) { + entry.setField(toField, fromFieldValue); + entry.setField(fromField, ""); + + edits.addEdit(new UndoableFieldChange(entry, fromField, fromFieldValue, null)); + edits.addEdit(new UndoableFieldChange(entry, toField, toFieldValue, fromFieldValue)); + affectedEntriesCount++; + } } } + edits.end(); } + + /** + * @return the number of affected entries + * */ + public int executeAndGetAffectedEntriesCount() { + execute(); + return affectedEntriesCount; + } } diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java b/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java new file mode 100644 index 00000000000..4fc8b9d8b63 --- /dev/null +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/NotificationPaneAdapter.java @@ -0,0 +1,23 @@ +package org.jabref.gui.edit.automaticfiededitor; + +import java.util.Collections; + +import javafx.scene.Node; +import javafx.util.Duration; + +import org.jabref.gui.LibraryTab; +import org.jabref.gui.icon.IconTheme; + +public class NotificationPaneAdapter extends LibraryTab.DatabaseNotification { + + public NotificationPaneAdapter(Node content) { + super(content); + } + + public void notify(int affectedEntries, int totalEntries) { + String notificationMessage = String.format("%d/%d affected entries", affectedEntries, totalEntries); + Node notificationGraphic = IconTheme.JabRefIcons.INTEGRITY_INFO.getGraphicNode(); + + notify(notificationGraphic, notificationMessage, Collections.emptyList(), Duration.seconds(2)); + } +} diff --git a/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsTab.fxml b/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTab.fxml similarity index 71% rename from src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsTab.fxml rename to src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTab.fxml index 7ac475e6cd7..462dcd0dfda 100644 --- a/src/main/java/org/jabref/gui/edit/automaticfiededitor/twofields/TwoFieldsTab.fxml +++ b/src/main/java/org/jabref/gui/edit/automaticfiededitor/copyormovecontent/CopyOrMoveFieldContentTab.fxml @@ -11,7 +11,7 @@ - + @@ -30,19 +30,23 @@