Skip to content

Commit

Permalink
Fix key generation for authorsAlpha and authShort for "and others"
Browse files Browse the repository at this point in the history
  • Loading branch information
koppor committed Apr 18, 2023
1 parent 29692e4 commit 557c284
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 72 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- We fixed the log text color in the event log console when using dark mode. [#9732](https://github.com/JabRef/jabref/issues/9732)
- We fixed an issue where searching for unlinked files would include the current library's .bib file [#9735](https://github.com/JabRef/jabref/issues/9735)
- We fixed an issue where it was no longer possible to connect to a shared mysql database due to an exception [#9761](https://github.com/JabRef/jabref/issues/9761)
- We fixed the BibTeX key generation for (`[authorsAlpha]`)[https://docs.jabref.org/setup/citationkeypatterns#special-field-markers] and `[authShort]` to handle `and others` properly. [koppor#626](https://github.com/koppor/jabref/issues/626)

### Removed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -792,10 +792,17 @@ private static String allAuthors(AuthorList authorList) {
* @param authorList an {@link AuthorList}
* @return the initials of all authors' names
*/
private static String authorsAlpha(AuthorList authorList) {
static String authorsAlpha(AuthorList authorList) {
StringBuilder alphaStyle = new StringBuilder();
int maxAuthors = authorList.getNumberOfAuthors() <= MAX_ALPHA_AUTHORS ?
authorList.getNumberOfAuthors() : (MAX_ALPHA_AUTHORS - 1);
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).getLastOnly()
Expand All @@ -808,8 +815,13 @@ private static String authorsAlpha(AuthorList authorList) {
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<String> vonAndLastNames = authorList.getAuthors().stream()
.limit(maxAuthors).map(Author::getLastOnly)
.limit(maxAuthors)
.map(Author::getLastOnly)
.collect(Collectors.toList());
for (String vonAndLast : vonAndLastNames) {
// replace all whitespaces by " "
Expand All @@ -820,7 +832,7 @@ private static String authorsAlpha(AuthorList authorList) {
alphaStyle.append(part, 0, 1);
}
}
if (authorList.getNumberOfAuthors() > MAX_ALPHA_AUTHORS) {
if (andOthersPresent || maxAuthorsExceeded) {
alphaStyle.append("+");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,23 @@
* This is the utility class of the LabelPattern package.
*/
public class CitationKeyGenerator extends BracketedPattern {
/*
/**
* All single characters that we can use for extending a key to make it unique.
*/
public static final String APPENDIX_CHARACTERS = "abcdefghijklmnopqrstuvwxyz";
public static final String DEFAULT_UNWANTED_CHARACTERS = "-`ʹ:!;?^+";

/**
* List of unwanted characters. These will be removed at the end.
* Note that <code>+</code> is a wanted character to indicate "et al." in authorsAlpha.
* Example: "ABC+". See {@link org.jabref.logic.citationkeypattern.BracketedPatternTest#authorsAlpha()} for examples.
*/
public static final String DEFAULT_UNWANTED_CHARACTERS = "-`ʹ:!;?^";

private static final Logger LOGGER = LoggerFactory.getLogger(CitationKeyGenerator.class);

// Source of disallowed characters : https://tex.stackexchange.com/a/408548/9075
private static final List<Character> DISALLOWED_CHARACTERS = Arrays.asList('{', '}', '(', ')', ',', '=', '\\', '"', '#', '%', '~', '\'');

private final AbstractCitationKeyPattern citeKeyPattern;
private final BibDatabase database;
private final CitationKeyPatternPreferences citationKeyPatternPreferences;
Expand Down
18 changes: 16 additions & 2 deletions src/main/java/org/jabref/logic/importer/AuthorListParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,17 @@ private static StringBuilder buildWithAffix(Collection<Integer> indexArray, List
public AuthorList parse(String listOfNames) {
Objects.requireNonNull(listOfNames);

// Handle the statement "and others" at the end, removing it
listOfNames = StringUtil.removeStringAtTheEnd(listOfNames.trim(), " and others");
// Handling of "and others"
// Remove it from the list; it will be added at the very end of this method as special Author.OTHERS
listOfNames = listOfNames.trim();
final String andOthersSuffix = " and others";
final boolean andOthersPresent;
if (StringUtil.endsWithIgnoreCase(listOfNames, andOthersSuffix)) {
andOthersPresent = true;
listOfNames = StringUtil.removeStringAtTheEnd(listOfNames, " and others");
} else {
andOthersPresent = false;
}

// Handle case names in order lastname, firstname and separated by ","
// E.g., Ali Babar, M., Dingsøyr, T., Lago, P., van der Vliet, H.
Expand Down Expand Up @@ -154,6 +163,11 @@ public AuthorList parse(String listOfNames) {
while (tokenStart < original.length()) {
getAuthor().ifPresent(authors::add);
}

if (andOthersPresent) {
authors.add(Author.OTHERS);
}

return AuthorList.of(authors);
}

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/jabref/model/entry/Author.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
* Current usage: only methods <code>getLastOnly</code>, <code>getFirstLast</code>, and <code>getLastFirst</code> are used; all other methods are provided for completeness.
*/
public class Author {

/**
* Object indicating the <code>others</code> author. This is a BibTeX feature mostly rendered in "et al." in LaTeX.
* Example: <code>authors = {Oliver Kopp and others}</code>. This is then appearing as "Oliver Kopp et al.".
* In the context of BibTeX key generation, this is "Kopp+" (<code>+</code> for "et al.") and not "KO".
*/
public static final Author OTHERS = new Author("", "", null, "others", null);

private final String firstPart;
private final String firstAbbr;
private final String vonPart;
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/jabref/model/strings/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -766,4 +766,9 @@ public static boolean containsWhitespace(String s) {
public static String removeStringAtTheEnd(String string, String stringToBeRemoved) {
return StringUtils.removeEndIgnoreCase(string, stringToBeRemoved);
}

@ApacheCommonsLang3Allowed("No Guava equivalent existing")
public static boolean endsWithIgnoreCase(String string, String suffix) {
return StringUtils.endsWithIgnoreCase(string, suffix);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.stream.Stream;

import org.jabref.model.database.BibDatabase;
import org.jabref.model.entry.AuthorList;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibtexString;
import org.jabref.model.entry.field.StandardField;
Expand Down Expand Up @@ -48,6 +49,44 @@ void setUp() {
database.insertEntry(dbentry);
}

static Stream<Arguments> authorsAlpha() {
return Stream.of(
Arguments.of("A+", "Alexander Artemenko and others"),
Arguments.of("ABC+", "Aachen and Berlin and Chemnitz and others"),
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"));
}

@ParameterizedTest
@MethodSource
void authorsAlpha(String expected, AuthorList list) {
assertEquals(expected, BracketedPattern.authorsAlpha(list));
}

private static Stream<Arguments> expandBracketsWithFallback() {
return Stream.of(
Arguments.of("auth", "[title:(auth)]"),
Arguments.of("auth2021", "[title:(auth[YEAR])]"),
Arguments.of("not2021", "[title:(not[YEAR])]"),
Arguments.of("", "[title:([YEAR)]"),
Arguments.of(")]", "[title:(YEAR])]"),
Arguments.of("2105.02891", "[title:([EPRINT:([YEAR])])]"),
Arguments.of("2021", "[title:([auth:([YEAR])])]")
);
}

@ParameterizedTest
@MethodSource()
void expandBracketsWithFallback(String expandResult, String pattern) {
BibEntry bibEntry = new BibEntry()
.withField(StandardField.YEAR, "2021").withField(StandardField.EPRINT, "2105.02891");
BracketedPattern bracketedPattern = new BracketedPattern(pattern);

assertEquals(expandResult, bracketedPattern.expand(bibEntry));
}

@Test
void bibentryExpansionTest() {
BracketedPattern pattern = new BracketedPattern("[year]_[auth]_[firstpage]");
Expand Down Expand Up @@ -348,18 +387,19 @@ void expandBracketsUnmodifiedStringFromLongLastPageNumber() {

@Test
void expandBracketsWithTestCasesFromRegExpBasedFileFinder() {
BibEntry entry = new BibEntry(StandardEntryType.Article).withCitationKey("HipKro03");
entry.setField(StandardField.AUTHOR, "Eric von Hippel and Georg von Krogh");
entry.setField(StandardField.TITLE, "Open Source Software and the \"Private-Collective\" Innovation Model: Issues for Organization Science");
entry.setField(StandardField.JOURNAL, "Organization Science");
entry.setField(StandardField.YEAR, "2003");
entry.setField(StandardField.VOLUME, "14");
entry.setField(StandardField.PAGES, "209--223");
entry.setField(StandardField.NUMBER, "2");
entry.setField(StandardField.ADDRESS, "Institute for Operations Research and the Management Sciences (INFORMS), Linthicum, Maryland, USA");
entry.setField(StandardField.DOI, "http://dx.doi.org/10.1287/orsc.14.2.209.14992");
entry.setField(StandardField.ISSN, "1526-5455");
entry.setField(StandardField.PUBLISHER, "INFORMS");
BibEntry entry = new BibEntry(StandardEntryType.Article)
.withCitationKey("HipKro03")
.withField(StandardField.AUTHOR, "Eric von Hippel and Georg von Krogh")
.withField(StandardField.TITLE, "Open Source Software and the \"Private-Collective\" Innovation Model: Issues for Organization Science")
.withField(StandardField.JOURNAL, "Organization Science")
.withField(StandardField.YEAR, "2003")
.withField(StandardField.VOLUME, "14")
.withField(StandardField.PAGES, "209--223")
.withField(StandardField.NUMBER, "2")
.withField(StandardField.ADDRESS, "Institute for Operations Research and the Management Sciences (INFORMS), Linthicum, Maryland, USA")
.withField(StandardField.DOI, "http://dx.doi.org/10.1287/orsc.14.2.209.14992")
.withField(StandardField.ISSN, "1526-5455")
.withField(StandardField.PUBLISHER, "INFORMS");

BibDatabase database = new BibDatabase();
database.insertEntry(entry);
Expand Down Expand Up @@ -392,26 +432,4 @@ void expandBracketsWithoutProtectiveBracesUsingUnprotectTermsModifier() {
.withField(StandardField.JOURNAL, "{ACS} Medicinal Chemistry Letters");
assertEquals("ACS Medicinal Chemistry Letters", BracketedPattern.expandBrackets("[JOURNAL:unprotect_terms]", null, bibEntry, null));
}

@ParameterizedTest
@MethodSource("provideArgumentsForFallback")
void expandBracketsWithFallback(String expandResult, String pattern) {
BibEntry bibEntry = new BibEntry()
.withField(StandardField.YEAR, "2021").withField(StandardField.EPRINT, "2105.02891");
BracketedPattern bracketedPattern = new BracketedPattern(pattern);

assertEquals(expandResult, bracketedPattern.expand(bibEntry));
}

private static Stream<Arguments> provideArgumentsForFallback() {
return Stream.of(
Arguments.of("auth", "[title:(auth)]"),
Arguments.of("auth2021", "[title:(auth[YEAR])]"),
Arguments.of("not2021", "[title:(not[YEAR])]"),
Arguments.of("", "[title:([YEAR)]"),
Arguments.of(")]", "[title:(YEAR])]"),
Arguments.of("2105.02891", "[title:([EPRINT:([YEAR])])]"),
Arguments.of("2021", "[title:([auth:([YEAR])])]")
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jabref.logic.citationkeypattern;

import java.util.Optional;
import java.util.stream.Stream;

import org.jabref.logic.importer.ImportFormatPreferences;
import org.jabref.logic.importer.ParseException;
Expand All @@ -13,22 +14,34 @@

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Answers;

import static org.jabref.logic.citationkeypattern.CitationKeyGenerator.DEFAULT_UNWANTED_CHARACTERS;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;

/**
* Tests whole citation key patterns such as <code>[authorsAlpha][year]</code>.
* The concrete patterns such as <code>authorsAlpha</code> should better be tested at {@link BracketedPatternTest}.
*/
class CitationKeyGeneratorTest {

private static final BibEntry AUTHOR_EMPTY = createABibEntryAuthor("");

private static final String AUTHOR_STRING_FIRSTNAME_FULL_LASTNAME_FULL_COUNT_1 = "Isaac Newton";
private static final BibEntry AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_COUNT_1 = createABibEntryAuthor("Isaac Newton");
private static final BibEntry AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_COUNT_1 = createABibEntryAuthor(AUTHOR_STRING_FIRSTNAME_FULL_LASTNAME_FULL_COUNT_1);

private static final BibEntry AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_COUNT_2 = createABibEntryAuthor("Isaac Newton and James Maxwell");

private static final String AUTHOR_STRING_FIRSTNAME_FULL_LASTNAME_FULL_COUNT_3 = "Isaac Newton and James Maxwell and Albert Einstein";
private static final BibEntry AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_COUNT_3 = createABibEntryAuthor("Isaac Newton and James Maxwell and Albert Einstein");
private static final BibEntry AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_COUNT_3 = createABibEntryAuthor(AUTHOR_STRING_FIRSTNAME_FULL_LASTNAME_FULL_COUNT_3);

private static final String AUTHOR_STRING_FIRSTNAME_FULL_LASTNAME_FULL_AND_OTHERS_COUNT_3 = "Isaac Newton and James Maxwell and others";
private static final BibEntry AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_AND_OTHERS_COUNT_3 = createABibEntryAuthor(AUTHOR_STRING_FIRSTNAME_FULL_LASTNAME_FULL_AND_OTHERS_COUNT_3);

private static final BibEntry AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_WITH_VAN_COUNT_1 = createABibEntryAuthor("Wil van der Aalst");
private static final BibEntry AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_WITH_VAN_COUNT_2 = createABibEntryAuthor("Wil van der Aalst and Tammo van Lessen");
Expand Down Expand Up @@ -542,7 +555,7 @@ void testAuthEtAl() {
@Test
void testAuthShort() {
// tests taken from the comments
assertEquals("NME", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_4, AUTHSHORT));
assertEquals("NME+", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_4, AUTHSHORT));
assertEquals("NME", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_3, AUTHSHORT));
assertEquals("NM", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_2, AUTHSHORT));
assertEquals("Newton", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_1, AUTHSHORT));
Expand Down Expand Up @@ -606,19 +619,23 @@ void testAllAuthors() {
assertEquals("NewtonMaxwellEinstein", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_3, AUTHORS));
}

/**
* Tests [authorsAlpha]
*/
@Test
void authorsAlpha() {
assertEquals("New", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_1, AUTHORSALPHA));
assertEquals("NM", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_2, AUTHORSALPHA));
assertEquals("NME", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_3, AUTHORSALPHA));
assertEquals("NMEB", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_4, AUTHORSALPHA));
assertEquals("NME", generateKey(AUTHOR_FIRSTNAME_INITIAL_LASTNAME_FULL_COUNT_5, AUTHORSALPHA));
static Stream<Arguments> testAuthorsAlpha() {
return Stream.of(
Arguments.of("New", 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)
);
}

assertEquals("vdAal", generateKey(AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_WITH_VAN_COUNT_1, AUTHORSALPHA));
assertEquals("vdAvL", generateKey(AUTHOR_FIRSTNAME_FULL_LASTNAME_FULL_WITH_VAN_COUNT_2, AUTHORSALPHA));
@ParameterizedTest
@MethodSource
void testAuthorsAlpha(String expected, BibEntry entry, String pattern) {
assertEquals(expected, generateKey(entry, pattern));
}

/**
Expand Down

0 comments on commit 557c284

Please sign in to comment.