diff --git a/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java b/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java index f2b1d393afd..2407cf0fe7f 100644 --- a/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java +++ b/src/main/java/org/jabref/logic/citationkeypattern/BracketedPattern.java @@ -21,6 +21,7 @@ import org.jabref.logic.cleanup.Formatter; import org.jabref.logic.formatter.Formatters; +import org.jabref.logic.formatter.bibtexfields.RemoveEnclosingBracesFormatter; import org.jabref.logic.formatter.casechanger.Word; import org.jabref.logic.layout.format.RemoveLatexCommandsFormatter; import org.jabref.model.database.BibDatabase; @@ -84,6 +85,8 @@ public class BracketedPattern { private static final Pattern WHITESPACE = Pattern.compile("\\p{javaWhitespace}"); + private static final RemoveEnclosingBracesFormatter ENCLOSING_BRACES_FORMATTER = new RemoveEnclosingBracesFormatter(); + private enum Institution { SCHOOL, DEPARTMENT, @@ -846,51 +849,44 @@ static String allAuthors(AuthorList authorList) { */ public static String authorsAlpha(AuthorList authorList) { StringBuilder alphaStyle = new StringBuilder(); - int maxAuthors; - final boolean maxAuthorsExceeded; - if (authorList.getNumberOfAuthors() <= MAX_ALPHA_AUTHORS) { - maxAuthors = authorList.getNumberOfAuthors(); - maxAuthorsExceeded = false; - } else { - maxAuthors = MAX_ALPHA_AUTHORS - 1; - maxAuthorsExceeded = true; - } - - if (authorList.getNumberOfAuthors() == 1) { - String[] firstAuthor = authorList.getAuthor(0).getNamePrefixAndFamilyName() - .replaceAll("\\s+", " ").trim().split(" "); - // take first letter of any "prefixes" (e.g. van der Aalst -> vd) - for (int j = 0; j < (firstAuthor.length - 1); j++) { - alphaStyle.append(firstAuthor[j], 0, 1); + int numberOfAuthors = authorList.getNumberOfAuthors(); + boolean andOthersPresent = numberOfAuthors > 1 && + authorList.getAuthor(numberOfAuthors - 1).equals(Author.OTHERS); + + if (numberOfAuthors == 1 || andOthersPresent) { + // Single author or "and others" case + String lastName = authorList.getAuthor(0).getFamilyName().orElse(""); + String formattedName = ENCLOSING_BRACES_FORMATTER.format(lastName); + if (!formattedName.equals(lastName)) { + // Inequality => braces were removed, indicating an organization + alphaStyle.append(getOrganizationInitials(formattedName)); + } else { + alphaStyle.append(lastName, 0, Math.min(2, lastName.length())); } - // append last part of last name completely - alphaStyle.append(firstAuthor[firstAuthor.length - 1], 0, - Math.min(3, firstAuthor[firstAuthor.length - 1].length())); } else { - boolean andOthersPresent = authorList.getAuthor(maxAuthors - 1).equals(Author.OTHERS); - if (andOthersPresent) { - maxAuthors--; - } - List vonAndLastNames = authorList.getAuthors().stream() - .limit(maxAuthors) - .map(Author::getNamePrefixAndFamilyName) - .toList(); - for (String vonAndLast : vonAndLastNames) { - // replace all whitespaces by " " - // split the lastname at " " - String[] nameParts = vonAndLast.replaceAll("\\s+", " ").trim().split(" "); - for (String part : nameParts) { - // use first character of each part of lastname - alphaStyle.append(part, 0, 1); + int maxAuthors = Math.min(numberOfAuthors, MAX_ALPHA_AUTHORS); + for (int i = 0; i < maxAuthors; i++) { + String lastName = authorList.getAuthor(i).getFamilyName().orElse(""); + alphaStyle.append(lastName, 0, 1); + if (alphaStyle.length() >= 4) { + // Stop after 4 authors + break; } } - if (andOthersPresent || maxAuthorsExceeded) { - alphaStyle.append("+"); - } } return alphaStyle.toString(); } + private static String getOrganizationInitials(String orgName) { + StringBuilder initials = new StringBuilder(); + for (String part : orgName.split("\\s+")) { + if (!part.isEmpty() && Character.isUpperCase(part.charAt(0))) { + initials.append(part.charAt(0)); + } + } + return initials.toString(); + } + /** * Creates a string with all last names separated by a `delimiter`. If the number of authors are larger than * `maxAuthors`, replace all excess authors with `suffix`. diff --git a/src/test/java/org/jabref/logic/citationkeypattern/AbstractCitationKeyPatternsTest.java b/src/test/java/org/jabref/logic/citationkeypattern/AbstractCitationKeyPatternsTest.java index f050f0fdc05..47c2666610b 100644 --- a/src/test/java/org/jabref/logic/citationkeypattern/AbstractCitationKeyPatternsTest.java +++ b/src/test/java/org/jabref/logic/citationkeypattern/AbstractCitationKeyPatternsTest.java @@ -12,7 +12,7 @@ class AbstractCitationKeyPatternsTest { @Test - void AbstractCitationKeyPatternParse() throws Exception { + void AbstractCitationKeyPatternParse() { AbstractCitationKeyPatterns pattern = mock(AbstractCitationKeyPatterns.class, Mockito.CALLS_REAL_METHODS); pattern.setDefaultValue("[field1]spacer1[field2]spacer2[field3]"); @@ -21,7 +21,7 @@ void AbstractCitationKeyPatternParse() throws Exception { } @Test - void AbstractCitationKeyPatternParseEmptySpacer() throws Exception { + void AbstractCitationKeyPatternParseEmptySpacer() { AbstractCitationKeyPatterns pattern = mock(AbstractCitationKeyPatterns.class, Mockito.CALLS_REAL_METHODS); pattern.setDefaultValue("[field1][field2]spacer2[field3]"); diff --git a/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java b/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java index f2893707f0e..cc466d6b90e 100644 --- a/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java +++ b/src/test/java/org/jabref/logic/citationkeypattern/BracketedPatternTest.java @@ -76,14 +76,27 @@ void allAuthors(String expected, AuthorList list) { static Stream authorsAlpha() { return Stream.of( - Arguments.of("A+", "Alexander Artemenko and others"), - Arguments.of("A+", "Aachen and others"), - Arguments.of("AB+", "Aachen and Berlin and others"), - Arguments.of("ABC+", "Aachen and Berlin and Chemnitz and others"), + Arguments.of("Ar", "Alexander Artemenko and others"), + Arguments.of("Aa", "Aachen and others"), + Arguments.of("Aa", "Aachen and Berlin and others"), + Arguments.of("Aa", "Aachen and Berlin and Chemnitz and others"), + Arguments.of("AB", "Aachen and Berlin"), + Arguments.of("ABC", "Aachen and Berlin and Chemnitz"), Arguments.of("ABCD", "Aachen and Berlin and Chemnitz and Düsseldorf"), - Arguments.of("ABC+", "Aachen and Berlin and Chemnitz and Düsseldorf and others"), - Arguments.of("ABC+", "Aachen and Berlin and Chemnitz and Düsseldorf and Essen"), - Arguments.of("ABC+", "Aachen and Berlin and Chemnitz and Düsseldorf and Essen and others")); + Arguments.of("Aa", "Aachen and Berlin and Chemnitz and Düsseldorf and others"), + Arguments.of("ABCD", "Aachen and Berlin and Chemnitz and Düsseldorf and Essen"), + Arguments.of("Aa", "Aachen and Berlin and Chemnitz and Düsseldorf and Essen and others"), + Arguments.of("AB", "Abel, K.; Bibel, U."), + Arguments.of("ABC", "Abraham, N.; Bibel, U.; Corleone, P."), + Arguments.of("Az", "Azubi, L. et.al."), + Arguments.of("Ez", "Ezgarani, O."), + Arguments.of("GI", "GI, Gesellschaft für Informatik e.V."), + Arguments.of("Gl", "Glück, H. I."), + Arguments.of("Go", "von Goethe"), + Arguments.of("Aa", "van der Aalst"), + Arguments.of("AW", "van der Aalst and Weske"), + Arguments.of("GI", "{Gesellschaft für Informatik e.V.}"), + Arguments.of("AF", "{Apache Foundation}")); } @ParameterizedTest @@ -305,7 +318,7 @@ void authShort(String expected, AuthorList list) { "'New', '[auth3]', 'Isaac Newton'", "'New', '[auth3_1]', 'Isaac Newton'", "'Newton', '[authshort]', 'Isaac Newton'", - "'New', '[authorsAlpha]', 'Isaac Newton'", + "'Ne', '[authorsAlpha]', 'Isaac Newton'", "'Newton', '[authorLast]', 'Isaac Newton'", "'I', '[authorLastForeIni]', 'Isaac Newton'", diff --git a/src/test/java/org/jabref/logic/citationkeypattern/CitationKeyGeneratorTest.java b/src/test/java/org/jabref/logic/citationkeypattern/CitationKeyGeneratorTest.java index 2afd2990190..c84f92c3019 100644 --- a/src/test/java/org/jabref/logic/citationkeypattern/CitationKeyGeneratorTest.java +++ b/src/test/java/org/jabref/logic/citationkeypattern/CitationKeyGeneratorTest.java @@ -505,14 +505,14 @@ void allAuthors() { static Stream authorsAlpha() { return Stream.of( - Arguments.of("New", AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_1, AUTHORSALPHA), + Arguments.of("Ne", AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_1, AUTHORSALPHA), Arguments.of("NM", AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_2, AUTHORSALPHA), Arguments.of("NME", AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_3, AUTHORSALPHA), Arguments.of("NMEB", AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_4, AUTHORSALPHA), - Arguments.of("NME+", AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_5, AUTHORSALPHA), - Arguments.of("vdAal", AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_WITH_VAN_COUNT_1, AUTHORSALPHA), - Arguments.of("vdAvL", AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_WITH_VAN_COUNT_2, AUTHORSALPHA), - Arguments.of("NM+", AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_AND_OTHERS_COUNT_3, AUTHORSALPHA) + Arguments.of("NMEB", AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_5, AUTHORSALPHA), + Arguments.of("Aa", AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_WITH_VAN_COUNT_1, AUTHORSALPHA), + Arguments.of("AL", AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_WITH_VAN_COUNT_2, AUTHORSALPHA), + Arguments.of("Ne", AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_AND_OTHERS_COUNT_3, AUTHORSALPHA) ); } diff --git a/src/test/java/org/jabref/model/entry/BibEntryTest.java b/src/test/java/org/jabref/model/entry/BibEntryTest.java index 25ed76d7633..0f81a583472 100644 --- a/src/test/java/org/jabref/model/entry/BibEntryTest.java +++ b/src/test/java/org/jabref/model/entry/BibEntryTest.java @@ -59,13 +59,13 @@ void setNullFieldThrowsNPE() { } @Test - void getFieldIsCaseInsensitive() throws Exception { + void getFieldIsCaseInsensitive() { entry.setField(new UnknownField("TeSt"), "value"); assertEquals(Optional.of("value"), entry.getField(new UnknownField("tEsT"))); } @Test - void getFieldWorksWithBibFieldAsWell() throws Exception { + void getFieldWorksWithBibFieldAsWell() { entry.setField(StandardField.AUTHOR, "value"); assertEquals(Optional.of("value"), entry.getField(new BibField(StandardField.AUTHOR, FieldPriority.IMPORTANT).field())); } @@ -76,80 +76,80 @@ void newBibEntryIsUnchanged() { } @Test - void setFieldLeadsToAChangedEntry() throws Exception { + void setFieldLeadsToAChangedEntry() { entry.setField(StandardField.AUTHOR, "value"); assertTrue(entry.hasChanged()); } @Test - void setFieldWorksWithBibFieldAsWell() throws Exception { + void setFieldWorksWithBibFieldAsWell() { entry.setField(new BibField(StandardField.AUTHOR, FieldPriority.IMPORTANT).field(), "value"); assertEquals(Optional.of("value"), entry.getField(StandardField.AUTHOR)); } @Test - void clonedBibEntryHasUniqueID() throws Exception { + void clonedBibEntryHasUniqueID() { BibEntry entryClone = (BibEntry) entry.clone(); assertNotEquals(entry.getId(), entryClone.getId()); } @Test - void clonedBibEntryWithMiscTypeHasOriginalChangedFlag() throws Exception { + void clonedBibEntryWithMiscTypeHasOriginalChangedFlag() { BibEntry entryClone = (BibEntry) entry.clone(); assertFalse(entryClone.hasChanged()); } @Test - void clonedBibEntryWithBookTypeAndOneFieldHasOriginalChangedFlag() throws Exception { + void clonedBibEntryWithBookTypeAndOneFieldHasOriginalChangedFlag() { entry = new BibEntry(StandardEntryType.Book).withField(StandardField.AUTHOR, "value"); BibEntry entryClone = (BibEntry) entry.clone(); assertFalse(entryClone.hasChanged()); } @Test - void setAndGetAreConsistentForMonth() throws Exception { + void setAndGetAreConsistentForMonth() { entry.setField(StandardField.MONTH, "may"); assertEquals(Optional.of("may"), entry.getField(StandardField.MONTH)); } @Test - void setAndGetAreConsistentForCapitalizedMonth() throws Exception { + void setAndGetAreConsistentForCapitalizedMonth() { entry.setField(StandardField.MONTH, "May"); assertEquals(Optional.of("May"), entry.getField(StandardField.MONTH)); } @Test - void setAndGetAreConsistentForMonthString() throws Exception { + void setAndGetAreConsistentForMonthString() { entry.setField(StandardField.MONTH, "#may#"); assertEquals(Optional.of("#may#"), entry.getField(StandardField.MONTH)); } @Test - void monthCorrectlyReturnedForMonth() throws Exception { + void monthCorrectlyReturnedForMonth() { entry.setField(StandardField.MONTH, "may"); assertEquals(Optional.of(Month.MAY), entry.getMonth()); } @Test - void monthCorrectlyReturnedForCapitalizedMonth() throws Exception { + void monthCorrectlyReturnedForCapitalizedMonth() { entry.setField(StandardField.MONTH, "May"); assertEquals(Optional.of(Month.MAY), entry.getMonth()); } @Test - void monthCorrectlyReturnedForMonthString() throws Exception { + void monthCorrectlyReturnedForMonthString() { entry.setField(StandardField.MONTH, "#may#"); assertEquals(Optional.of(Month.MAY), entry.getMonth()); } @Test - void monthCorrectlyReturnedForMonthMay() throws Exception { + void monthCorrectlyReturnedForMonthMay() { entry.setMonth(Month.MAY); assertEquals(Optional.of(Month.MAY), entry.getMonth()); } @Test - void monthFieldCorrectlyReturnedForMonthMay() throws Exception { + void monthFieldCorrectlyReturnedForMonthMay() { entry.setMonth(Month.MAY); assertEquals(Optional.of("#may#"), entry.getField(StandardField.MONTH)); } @@ -362,23 +362,23 @@ void isEmptyCiteKey() { } @Test - void identicObjectsareEqual() throws Exception { + void identicObjectsareEqual() { BibEntry otherEntry = entry; assertEquals(entry, otherEntry); } @Test - void compareToNullObjectIsFalse() throws Exception { + void compareToNullObjectIsFalse() { assertNotEquals(null, entry); } @Test - void compareToDifferentClassIsFalse() throws Exception { + void compareToDifferentClassIsFalse() { assertNotEquals(entry, new Object()); } @Test - void compareIsTrueWhenIdAndFieldsAreEqual() throws Exception { + void compareIsTrueWhenIdAndFieldsAreEqual() { entry.setId("1"); entry.setField(new UnknownField("key"), "value"); BibEntry otherEntry = new BibEntry();