diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d3a974c382..a6ffe9ebcee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We implemented an option to download fulltext files while importing. [#6381](https://github.com/JabRef/jabref/pull/6381) - We added a progress-indicator showing the average progress of background tasks to the toolbar. Clicking it reveals a pop-over with a list of running background tasks. [6443](https://github.com/JabRef/jabref/pull/6443) - We fixed the bug when strike the delete key in the text field. [#6421](https://github.com/JabRef/jabref/issues/6421) +- We added a BibTex key modifier for truncating strings. [#3915](https://github.com/JabRef/jabref/issues/3915) - We added support for jumping to target entry when typing letter/digit after sorting a column in maintable [#6146](https://github.com/JabRef/jabref/issues/6146) ### Changed @@ -57,6 +58,8 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We fixed an issue where JabRef switched to discrete graphics under macOS [#5935](https://github.com/JabRef/jabref/issues/5935) - We fixed an issue where the Preferences entry preview will be unexpected modified leads to Value too long exception [#6198](https://github.com/JabRef/jabref/issues/6198) - We fixed an issue where custom jstyles for Open/LibreOffice would only be valid if a layout line for the entry type `default` was at the end of the layout section [#6303](https://github.com/JabRef/jabref/issues/6303) +- We fixed an issue where long directory names created from patterns could create an exception. [#3915](https://github.com/JabRef/jabref/issues/3915) + - We fixed an issue where sort on numeric cases was broken. [#6349](https://github.com/JabRef/jabref/issues/6349) - We fixed an issue where an "Not on FX thread" exception occured when saving on linux [#6453](https://github.com/JabRef/jabref/issues/6453) - We fixed an issue where the library sort order was lost. [#6091](https://github.com/JabRef/jabref/issues/6091) diff --git a/src/main/java/org/jabref/logic/formatter/Formatters.java b/src/main/java/org/jabref/logic/formatter/Formatters.java index b4ea94dad43..405c854a1ec 100644 --- a/src/main/java/org/jabref/logic/formatter/Formatters.java +++ b/src/main/java/org/jabref/logic/formatter/Formatters.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.regex.Pattern; import org.jabref.logic.formatter.bibtexfields.CleanupUrlFormatter; import org.jabref.logic.formatter.bibtexfields.ClearFormatter; @@ -29,10 +30,12 @@ import org.jabref.logic.formatter.casechanger.TitleCaseFormatter; import org.jabref.logic.formatter.casechanger.UpperCaseFormatter; import org.jabref.logic.formatter.minifier.MinifyNameListFormatter; +import org.jabref.logic.formatter.minifier.TruncateFormatter; import org.jabref.logic.layout.format.LatexToUnicodeFormatter; import org.jabref.model.cleanup.Formatter; public class Formatters { + private static final Pattern TRUNCATE_PATTERN = Pattern.compile("\\Atruncate\\d+\\z"); private Formatters() { } @@ -102,6 +105,9 @@ public static Optional getFormatterForModifier(String modifier) { if (modifier.startsWith(RegexFormatter.KEY)) { String regex = modifier.substring(RegexFormatter.KEY.length()); return Optional.of(new RegexFormatter(regex)); + } else if (TRUNCATE_PATTERN.matcher(modifier).matches()) { + int truncateAfter = Integer.parseInt(modifier.substring(8)); + return Optional.of(new TruncateFormatter(truncateAfter)); } else { return getAll().stream().filter(f -> f.getKey().equals(modifier)).findAny(); } diff --git a/src/main/java/org/jabref/logic/formatter/minifier/TruncateFormatter.java b/src/main/java/org/jabref/logic/formatter/minifier/TruncateFormatter.java new file mode 100644 index 00000000000..200fcf3e867 --- /dev/null +++ b/src/main/java/org/jabref/logic/formatter/minifier/TruncateFormatter.java @@ -0,0 +1,51 @@ +package org.jabref.logic.formatter.minifier; + +import java.util.Objects; + +import org.jabref.logic.l10n.Localization; +import org.jabref.model.cleanup.Formatter; + +public class TruncateFormatter extends Formatter { + private final int TRUNCATE_AFTER; + private final String KEY; + + /** + * The TruncateFormatter truncates a string after the given index and removes trailing whitespaces. + * + * @param truncateIndex truncate a string after this index. + */ + public TruncateFormatter(final int truncateIndex) { + TRUNCATE_AFTER = (truncateIndex >= 0) ? truncateIndex : Integer.MAX_VALUE; + KEY = "truncate" + TRUNCATE_AFTER; + } + + @Override + public String getName() { + return Localization.lang("Truncate"); + } + + @Override + public String getKey() { + return KEY; + } + + /** + * Truncates a string after the given index. + */ + @Override + public String format(final String input) { + Objects.requireNonNull(input); + final int index = Math.min(TRUNCATE_AFTER, input.length()); + return input.substring(0, index).stripTrailing(); + } + + @Override + public String getDescription() { + return Localization.lang("Truncates a string after a given index."); + } + + @Override + public String getExampleInput() { + return "Truncate this sentence."; + } +} diff --git a/src/main/java/org/jabref/logic/util/io/FileUtil.java b/src/main/java/org/jabref/logic/util/io/FileUtil.java index f8d3765cdf8..107f18b5cac 100644 --- a/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -273,22 +273,23 @@ public static String createFileNameFromPattern(BibDatabase database, BibEntry en } /** - * Determines filename provided by an entry in a database + * Determines directory name provided by an entry in a database * * @param database the database, where the entry is located - * @param entry the entry to which the file should be linked to - * @param fileNamePattern the filename pattern - * @return a suggested fileName + * @param entry the entry to which the directory should be linked to + * @param directoryNamePattern the dirname pattern + * @return a suggested dirName */ - public static String createDirNameFromPattern(BibDatabase database, BibEntry entry, String fileNamePattern) { - String targetName = BracketedPattern.expandBrackets(fileNamePattern, ';', entry, database); + public static String createDirNameFromPattern(BibDatabase database, BibEntry entry, String directoryNamePattern) { + String targetName = BracketedPattern.expandBrackets(directoryNamePattern, ';', entry, database); if (targetName.isEmpty()) { targetName = entry.getCiteKeyOptional().orElse("default"); } - // Removes illegal characters from filename + // Removes illegal characters from directory name targetName = FileNameCleaner.cleanDirectoryName(targetName); + return targetName; } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 22c3c891ddf..cbf6b22bb31 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2241,5 +2241,6 @@ Computer\ software.\ The\ standard\ styles\ will\ treat\ this\ entry\ type\ as\ A\ data\ set\ or\ a\ similar\ collection\ of\ (mostly)\ raw\ data.=A data set or a similar collection of (mostly) raw data. Display\ count\ of\ items\ in\ group=Display count of items in group - Remove\ the\ following\ characters\:=Remove the following characters: +Truncate=Truncate +Truncates\ a\ string\ after\ a\ given\ index.=Truncates a string after a given index. diff --git a/src/test/java/org/jabref/logic/util/BracketedPatternTest.java b/src/test/java/org/jabref/logic/bibtexkeypattern/BracketedPatternTest.java similarity index 94% rename from src/test/java/org/jabref/logic/util/BracketedPatternTest.java rename to src/test/java/org/jabref/logic/bibtexkeypattern/BracketedPatternTest.java index e142c4fb270..5a043cf3324 100644 --- a/src/test/java/org/jabref/logic/util/BracketedPatternTest.java +++ b/src/test/java/org/jabref/logic/bibtexkeypattern/BracketedPatternTest.java @@ -1,6 +1,5 @@ -package org.jabref.logic.util; +package org.jabref.logic.bibtexkeypattern; -import org.jabref.logic.bibtexkeypattern.BracketedPattern; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibtexString; @@ -21,7 +20,7 @@ class BracketedPatternTest { private BibEntry dbentry; @BeforeEach - void setUp() throws Exception { + void setUp() { bibentry = new BibEntry(); bibentry.setField(StandardField.AUTHOR, "O. Kitsune"); bibentry.setField(StandardField.YEAR, "2017"); @@ -253,4 +252,19 @@ void testEmptyBrackets() { assertEquals("2003-Organization Science", BracketedPattern.expandBrackets("[year][]-[journal]", ';', dbentry, database)); } + + /** + * Test the [:truncate] modifier + */ + @Test + void expandBracketsChainsTwoTruncateModifiers() { + assertEquals("Open", + BracketedPattern.expandBrackets("[fulltitle:truncate6:truncate5]", ';', dbentry, database)); + } + + @Test + void expandBracketsDoesNotTruncateWithoutAnArgumentToTruncateModifier() { + assertEquals("Open Source Software and the \"Private-Collective\" Innovation Model: Issues for Organization Science", + BracketedPattern.expandBrackets("[fulltitle:truncate]", ';', dbentry, database)); + } } diff --git a/src/test/java/org/jabref/logic/formatter/FormatterTest.java b/src/test/java/org/jabref/logic/formatter/FormatterTest.java index 316fc9142c7..412483c5907 100644 --- a/src/test/java/org/jabref/logic/formatter/FormatterTest.java +++ b/src/test/java/org/jabref/logic/formatter/FormatterTest.java @@ -6,6 +6,7 @@ import java.util.stream.Stream; import org.jabref.logic.formatter.casechanger.ProtectTermsFormatter; +import org.jabref.logic.formatter.minifier.TruncateFormatter; import org.jabref.logic.protectedterms.ProtectedTermsLoader; import org.jabref.logic.protectedterms.ProtectedTermsPreferences; import org.jabref.model.cleanup.Formatter; @@ -111,10 +112,12 @@ public static Stream getFormatters() { Formatters.getAll().stream(), // following formatters are not contained in the list of all formatters, because // - the IdentityFormatter is not offered to the user, - // - the ProtectTermsFormatter needs more configuration + // - the ProtectTermsFormatter needs more configuration, + // - the TruncateFormatter needs setup, Stream.of( new IdentityFormatter(), - new ProtectTermsFormatter(protectedTermsLoader))); + new ProtectTermsFormatter(protectedTermsLoader), + new TruncateFormatter(0))); // @formatter:on } } diff --git a/src/test/java/org/jabref/logic/formatter/minifier/TruncateFormatterTest.java b/src/test/java/org/jabref/logic/formatter/minifier/TruncateFormatterTest.java new file mode 100644 index 00000000000..e6a5f50b1f3 --- /dev/null +++ b/src/test/java/org/jabref/logic/formatter/minifier/TruncateFormatterTest.java @@ -0,0 +1,55 @@ +package org.jabref.logic.formatter.minifier; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests in addition to the general tests from {@link org.jabref.logic.formatter.FormatterTest} + */ +public class TruncateFormatterTest { + private final String TITLE = "A Title"; + + @Test + void formatWorksWith0Index() { + TruncateFormatter formatter = new TruncateFormatter(0); + assertEquals("", formatter.format(TITLE)); + } + + @Test + void formatRemovesTrailingWhitespace() { + TruncateFormatter formatter = new TruncateFormatter(2); + assertEquals("A", formatter.format(TITLE)); + } + + @Test + void formatKeepsInternalWhitespace() { + TruncateFormatter formatter = new TruncateFormatter(3); + assertEquals("A T", formatter.format(TITLE)); + } + + @Test + void formatWorksWith9999Length() { + TruncateFormatter formatter = new TruncateFormatter(9999); + assertEquals(TITLE, formatter.format(TITLE)); + } + + @Test + void formatIgnoresNegativeIndex() { + TruncateFormatter formatter = new TruncateFormatter(-1); + assertEquals(TITLE, formatter.format(TITLE)); + } + + @Test + void formatWorksWithEmptyString() { + TruncateFormatter formatter = new TruncateFormatter(9999); + assertEquals("", formatter.format("")); + } + + @Test + void formatThrowsExceptionNullString() { + TruncateFormatter formatter = new TruncateFormatter(9999); + assertThrows(NullPointerException.class, () -> formatter.format(null)); + } +}