package org.jabref.logic.openoffice; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.regex.Pattern; import org.jabref.logic.layout.Layout; import org.jabref.logic.layout.LayoutFormatter; import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.logic.layout.LayoutHelper; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; import org.jabref.model.strings.StringUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This class embodies a bibliography formatting for OpenOffice, which is composed * of the following elements: * <p> * 1) Each OO BIB entry type must have a formatting. A formatting is an array of elements, each * of which is either a piece of constant text, an entry field value, or a tab. Each element has * a character format associated with it. * <p> * 2) Many field values (e.g. author) need to be formatted before input to OpenOffice. The style * has the responsibility of formatting all field values. Formatting is handled by 0-n * JabRef LayoutFormatter classes. * <p> * 3) If the entries are not numbered, a citation marker must be produced for each entry. This * operation is performed for each JabRef BibEntry. */ public class OOBibStyle implements Comparable<OOBibStyle> { public static final String ITALIC_ET_AL = "ItalicEtAl"; public static final String MULTI_CITE_CHRONOLOGICAL = "MultiCiteChronological"; public static final String MINIMUM_GROUPING_COUNT = "MinimumGroupingCount"; public static final String ET_AL_STRING = "EtAlString"; public static final String MAX_AUTHORS_FIRST = "MaxAuthorsFirst"; public static final String REFERENCE_HEADER_PARAGRAPH_FORMAT = "ReferenceHeaderParagraphFormat"; public static final String REFERENCE_PARAGRAPH_FORMAT = "ReferenceParagraphFormat"; public static final String TITLE = "Title"; public static final String UNDEFINED_CITATION_MARKER = "??"; private static final Pattern NUM_PATTERN = Pattern.compile("-?\\d+"); private static final String LAYOUT_MRK = "LAYOUT"; private static final String PROPERTIES_MARK = "PROPERTIES"; private static final String CITATION_MARK = "CITATION"; private static final String NAME_MARK = "NAME"; private static final String JOURNALS_MARK = "JOURNALS"; private static final String DEFAULT_MARK = "default"; private static final String BRACKET_AFTER_IN_LIST = "BracketAfterInList"; private static final String BRACKET_BEFORE_IN_LIST = "BracketBeforeInList"; private static final String UNIQUEFIER_SEPARATOR = "UniquefierSeparator"; private static final String BIBTEX_KEY_CITATIONS = "BibTeXKeyCitations"; private static final String SUBSCRIPT_CITATIONS = "SubscriptCitations"; private static final String SUPERSCRIPT_CITATIONS = "SuperscriptCitations"; private static final String BOLD_CITATIONS = "BoldCitations"; private static final String ITALIC_CITATIONS = "ItalicCitations"; private static final String CITATION_CHARACTER_FORMAT = "CitationCharacterFormat"; private static final String FORMAT_CITATIONS = "FormatCitations"; private static final String GROUPED_NUMBERS_SEPARATOR = "GroupedNumbersSeparator"; private static final String PAGE_INFO_SEPARATOR = "PageInfoSeparator"; private static final String CITATION_SEPARATOR = "CitationSeparator"; private static final String IN_TEXT_YEAR_SEPARATOR = "InTextYearSeparator"; private static final String MAX_AUTHORS = "MaxAuthors"; private static final String YEAR_FIELD = "YearField"; private static final String AUTHOR_FIELD = "AuthorField"; private static final String BRACKET_AFTER = "BracketAfter"; private static final String BRACKET_BEFORE = "BracketBefore"; private static final String IS_NUMBER_ENTRIES = "IsNumberEntries"; private static final String IS_SORT_BY_POSITION = "IsSortByPosition"; private static final String SORT_ALGORITHM = "SortAlgorithm"; private static final String OXFORD_COMMA = "OxfordComma"; private static final String YEAR_SEPARATOR = "YearSeparator"; private static final String AUTHOR_LAST_SEPARATOR_IN_TEXT = "AuthorLastSeparatorInText"; private static final String AUTHOR_LAST_SEPARATOR = "AuthorLastSeparator"; private static final String AUTHOR_SEPARATOR = "AuthorSeparator"; private static final Pattern QUOTED = Pattern.compile("\".*\""); private static final Log LOGGER = LogFactory.getLog(OOBibStyle.class); private String name = ""; private final SortedSet<String> journals = new TreeSet<>(); // Formatter to be run on fields before they are used as part of citation marker: private final LayoutFormatter fieldFormatter = new OOPreFormatter(); private Layout defaultBibLayout; // reference layout mapped from entry type number: private final Map<String, Layout> bibLayout = new HashMap<>(); private final Map<String, Object> properties = new HashMap<>(); private final Map<String, Object> citProperties = new HashMap<>(); private boolean valid; private final boolean fromResource; private final String path; enum BibStyleMode { NONE, LAYOUT, PROPERTIES, CITATION, NAME, JOURNALS } private File styleFile; private final Charset encoding; private long styleFileModificationTime = Long.MIN_VALUE; private String localCopy; private final LayoutFormatterPreferences prefs; public OOBibStyle(File styleFile, LayoutFormatterPreferences prefs, Charset encoding) throws IOException { this.prefs = Objects.requireNonNull(prefs); this.styleFile = Objects.requireNonNull(styleFile); this.encoding = Objects.requireNonNull(encoding); setDefaultProperties(); reload(); fromResource = false; path = styleFile.getPath(); } public OOBibStyle(String resourcePath, LayoutFormatterPreferences prefs) throws IOException { this.prefs = Objects.requireNonNull(prefs); Objects.requireNonNull(resourcePath); this.encoding = StandardCharsets.UTF_8; setDefaultProperties(); initialize(OOBibStyle.class.getResourceAsStream(resourcePath)); fromResource = true; path = resourcePath; } private void setDefaultProperties() { // Set default property values: properties.put(TITLE, "Bibliography"); properties.put(SORT_ALGORITHM, "alphanumeric"); properties.put(IS_SORT_BY_POSITION, Boolean.FALSE); properties.put(IS_NUMBER_ENTRIES, Boolean.FALSE); properties.put(BRACKET_BEFORE, "["); properties.put(BRACKET_AFTER, "]"); properties.put(REFERENCE_PARAGRAPH_FORMAT, "Default"); properties.put(REFERENCE_HEADER_PARAGRAPH_FORMAT, "Heading 1"); // Set default properties for the citation marker: citProperties.put(AUTHOR_FIELD, FieldName.orFields(FieldName.AUTHOR, FieldName.EDITOR)); citProperties.put(YEAR_FIELD, FieldName.YEAR); citProperties.put(MAX_AUTHORS, 3); citProperties.put(MAX_AUTHORS_FIRST, -1); citProperties.put(AUTHOR_SEPARATOR, ", "); citProperties.put(AUTHOR_LAST_SEPARATOR, " & "); citProperties.put(AUTHOR_LAST_SEPARATOR_IN_TEXT, null); citProperties.put(ET_AL_STRING, " et al."); citProperties.put(YEAR_SEPARATOR, ", "); citProperties.put(IN_TEXT_YEAR_SEPARATOR, " "); citProperties.put(BRACKET_BEFORE, "("); citProperties.put(BRACKET_AFTER, ")"); citProperties.put(CITATION_SEPARATOR, "; "); citProperties.put(PAGE_INFO_SEPARATOR, "; "); citProperties.put(GROUPED_NUMBERS_SEPARATOR, "-"); citProperties.put(MINIMUM_GROUPING_COUNT, 3); citProperties.put(FORMAT_CITATIONS, Boolean.FALSE); citProperties.put(CITATION_CHARACTER_FORMAT, "Default"); citProperties.put(ITALIC_CITATIONS, Boolean.FALSE); citProperties.put(BOLD_CITATIONS, Boolean.FALSE); citProperties.put(SUPERSCRIPT_CITATIONS, Boolean.FALSE); citProperties.put(SUBSCRIPT_CITATIONS, Boolean.FALSE); citProperties.put(MULTI_CITE_CHRONOLOGICAL, Boolean.TRUE); citProperties.put(BIBTEX_KEY_CITATIONS, Boolean.FALSE); citProperties.put(ITALIC_ET_AL, Boolean.FALSE); citProperties.put(OXFORD_COMMA, ""); } public String getName() { return name; } public String getPath() { return path; } public File getFile() { return styleFile; } public Set<String> getJournals() { return Collections.unmodifiableSet(journals); } private void initialize(InputStream stream) throws IOException { Objects.requireNonNull(stream); try (Reader reader = new InputStreamReader(stream, encoding)) { readFormatFile(reader); } } /** * If this style was initialized from a file on disk, reload the style * if the file has been modified since it was read. * * @throws IOException */ public void ensureUpToDate() throws IOException { if (!isUpToDate()) { reload(); } } /** * If this style was initialized from a file on disk, reload the style * information. * * @throws IOException */ private void reload() throws IOException { if (styleFile != null) { this.styleFileModificationTime = styleFile.lastModified(); try (InputStream stream = new FileInputStream(styleFile)) { initialize(stream); } } } /** * If this style was initialized from a file on disk, check whether the file * is unmodified since initialization. * * @return true if the file has not been modified, false otherwise. */ private boolean isUpToDate() { if (styleFile == null) { return true; } else { return styleFile.lastModified() == this.styleFileModificationTime; } } private void readFormatFile(Reader in) throws IOException { // First read all the contents of the file: StringBuilder sb = new StringBuilder(); int c; while ((c = in.read()) != -1) { sb.append((char) c); } // Store a local copy for viewing localCopy = sb.toString(); // Break into separate lines: String[] lines = sb.toString().split("\n"); BibStyleMode mode = BibStyleMode.NONE; for (String line1 : lines) { String line = line1; if (!line.isEmpty() && (line.charAt(line.length() - 1) == '\r')) { line = line.substring(0, line.length() - 1); } // Check for empty line or comment: if (line.trim().isEmpty() || (line.charAt(0) == '#')) { continue; } // Check if we should change mode: switch (line) { case NAME_MARK: mode = BibStyleMode.NAME; continue; case LAYOUT_MRK: mode = BibStyleMode.LAYOUT; continue; case PROPERTIES_MARK: mode = BibStyleMode.PROPERTIES; continue; case CITATION_MARK: mode = BibStyleMode.CITATION; continue; case JOURNALS_MARK: mode = BibStyleMode.JOURNALS; continue; default: break; } switch (mode) { case NAME: if (!line.trim().isEmpty()) { name = line.trim(); } break; case LAYOUT: handleStructureLine(line); break; case PROPERTIES: handlePropertiesLine(line, properties); break; case CITATION: handlePropertiesLine(line, citProperties); break; case JOURNALS: handleJournalsLine(line); break; default: break; } } // Set validity boolean based on whether we found anything interesting // in the file: if (mode != BibStyleMode.NONE) { valid = true; } } /** * After initializing this style from a file, this method can be used to check * whether the file appeared to be a proper style file. * * @return true if the file could be parsed as a style file, false otherwise. */ public boolean isValid() { return valid; } /** * Parse a line providing bibliography structure information for an entry type. * * @param line The string containing the structure description. * @throws IOException */ private void handleStructureLine(String line) { int index = line.indexOf('='); if ((index > 0) && (index < (line.length() - 1))) { String formatString = line.substring(index + 1); boolean setDefault = line.substring(0, index).equals(OOBibStyle.DEFAULT_MARK); String type = line.substring(0, index); try { Layout layout = new LayoutHelper(new StringReader(formatString), this.prefs).getLayoutFromText(); if (setDefault) { defaultBibLayout = layout; } else { bibLayout.put(type.toLowerCase(Locale.ENGLISH), layout); } } catch (IOException ex) { LOGGER.warn("Cannot parse bibliography structure", ex); } } } /** * Parse a line providing a property name and value. * * @param line The line containing the formatter names. * @throws IOException */ private void handlePropertiesLine(String line, Map<String, Object> map) { int index = line.indexOf('='); if ((index > 0) && (index <= (line.length() - 1))) { String propertyName = line.substring(0, index).trim(); String value = line.substring(index + 1); if ((value.trim().length() > 1) && QUOTED.matcher(value.trim()).matches()) { value = value.trim().substring(1, value.trim().length() - 1); } Object toSet = value; if (NUM_PATTERN.matcher(value).matches()) { toSet = Integer.parseInt(value); } else if ("true".equalsIgnoreCase(value.trim())) { toSet = Boolean.TRUE; } else if ("false".equalsIgnoreCase(value.trim())) { toSet = Boolean.FALSE; } map.put(propertyName, toSet); } } /** * Parse a line providing a journal name for which this style is valid. * * @param line * @throws IOException */ private void handleJournalsLine(String line) { if (!line.trim().isEmpty()) { journals.add(line.trim()); } } public Layout getReferenceFormat(String type) { Layout l = bibLayout.get(type.toLowerCase(Locale.ENGLISH)); if (l == null) { return defaultBibLayout; } else { return l; } } /** * Format a number-based citation marker for the given number. * * @param number The citation numbers. * @return The text for the citation. */ public String getNumCitationMarker(List<Integer> number, int minGroupingCount, boolean inList) { String bracketBefore = getStringCitProperty(BRACKET_BEFORE); if (inList && (citProperties.containsKey(BRACKET_BEFORE_IN_LIST))) { bracketBefore = getStringCitProperty(BRACKET_BEFORE_IN_LIST); } String bracketAfter = getStringCitProperty(BRACKET_AFTER); if (inList && (citProperties.containsKey(BRACKET_AFTER_IN_LIST))) { bracketAfter = getStringCitProperty(BRACKET_AFTER_IN_LIST); } // Sort the numbers: List<Integer> lNum = new ArrayList<>(number); Collections.sort(lNum); StringBuilder sb = new StringBuilder(bracketBefore); int combineFrom = -1; int written = 0; for (int i = 0; i < lNum.size(); i++) { int i1 = lNum.get(i); if (combineFrom < 0) { // Check if next entry is the next in the ref list: if ((i < (lNum.size() - 1)) && (lNum.get(i + 1) == (i1 + 1)) && (i1 > 0)) { combineFrom = i1; } else { // Add single entry: if (i > 0) { sb.append(getStringCitProperty(CITATION_SEPARATOR)); } sb.append(lNum.get(i) > 0 ? String.valueOf(lNum.get(i)) : OOBibStyle.UNDEFINED_CITATION_MARKER); written++; } } else { // We are building a list of combined entries. // Check if it ends here: if ((i == (lNum.size() - 1)) || (lNum.get(i + 1) != (i1 + 1))) { if (written > 0) { sb.append(getStringCitProperty(CITATION_SEPARATOR)); } if ((minGroupingCount > 0) && (((i1 + 1) - combineFrom) >= minGroupingCount)) { sb.append(combineFrom); sb.append(getStringCitProperty(GROUPED_NUMBERS_SEPARATOR)); sb.append(i1); written++; } else { // Either we should never group, or there aren't enough // entries in this case to group. Output all: for (int jj = combineFrom; jj <= i1; jj++) { sb.append(jj); if (jj < i1) { sb.append(getStringCitProperty(CITATION_SEPARATOR)); } written++; } } combineFrom = -1; } // If it doesn't end here, just keep iterating. } } sb.append(bracketAfter); return sb.toString(); } /** * Format the marker for the in-text citation according to this BIB style. Uniquefier letters are added as * provided by the uniquefiers argument. If successive entries within the citation are uniquefied from each other, * this method will perform a grouping of these entries. * * @param entries The list of JabRef BibEntry providing the data. * @param database A map of BibEntry-BibDatabase pairs. * @param inParenthesis Signals whether a parenthesized citation or an in-text citation is wanted. * @param uniquefiers Strings to add behind the year for each entry in case it's needed to separate similar * entries. * @param unlimAuthors Boolean for each entry. If true, we should not use "et al" formatting regardless * of the number of authors. Can be null to indicate that no entries should have unlimited names. * @return The formatted citation. */ public String getCitationMarker(List<BibEntry> entries, Map<BibEntry, BibDatabase> database, boolean inParenthesis, String[] uniquefiers, int[] unlimAuthors) { // Look for groups of uniquefied entries that should be combined in the output. // E.g. (Olsen, 2005a, b) should be output instead of (Olsen, 2005a; Olsen, 2005b). int piv = -1; String tmpMarker = null; if (uniquefiers != null) { for (int i = 0; i < uniquefiers.length; i++) { if ((uniquefiers[i] == null) || uniquefiers[i].isEmpty()) { // This entry has no uniquefier. // Check if we just passed a group of more than one entry with uniquefier: if ((piv > -1) && (i > (piv + 1))) { // Do the grouping: group(entries, uniquefiers, piv, i - 1); } piv = -1; } else { BibEntry currentEntry = entries.get(i); if (piv == -1) { piv = i; tmpMarker = getAuthorYearParenthesisMarker(Collections.singletonList(currentEntry), database, null, unlimAuthors); } else { // See if this entry can go into a group with the previous one: String thisMarker = getAuthorYearParenthesisMarker(Collections.singletonList(currentEntry), database, null, unlimAuthors); String authorField = getStringCitProperty(AUTHOR_FIELD); int maxAuthors = getIntCitProperty(MAX_AUTHORS); String author = getCitationMarkerField(currentEntry, database.get(currentEntry), authorField); AuthorList al = AuthorList.parse(author); int prevALim = unlimAuthors[i - 1]; // i always at least 1 here if (!thisMarker.equals(tmpMarker) || ((al.getNumberOfAuthors() > maxAuthors) && (unlimAuthors[i] != prevALim))) { // No match. Update piv to exclude the previous entry. But first check if the // previous entry was part of a group: if ((piv > -1) && (i > (piv + 1))) { // Do the grouping: group(entries, uniquefiers, piv, i - 1); } tmpMarker = thisMarker; piv = i; } } } } // Finished with the loop. See if the last entries form a group: if (piv >= 0) { // Do the grouping: group(entries, uniquefiers, piv, uniquefiers.length - 1); } } if (inParenthesis) { return getAuthorYearParenthesisMarker(entries, database, uniquefiers, unlimAuthors); } else { return getAuthorYearInTextMarker(entries, database, uniquefiers, unlimAuthors); } } /** * Modify entry and uniquefier arrays to facilitate a grouped presentation of uniquefied entries. * * @param entries The entry array. * @param uniquefiers The uniquefier array. * @param from The first index to group (inclusive) * @param to The last index to group (inclusive) */ private void group(List<BibEntry> entries, String[] uniquefiers, int from, int to) { String separator = getStringCitProperty(UNIQUEFIER_SEPARATOR); StringBuilder sb = new StringBuilder(uniquefiers[from]); for (int i = from + 1; i <= to; i++) { sb.append(separator); sb.append(uniquefiers[i]); entries.set(i, null); } uniquefiers[from] = sb.toString(); } /** * This method produces (Author, year) style citation strings in many different forms. * * @param entries The list of BibEntry to get fields from. * @param database A map of BibEntry-BibDatabase pairs. * @param uniquifiers Optional parameter to separate similar citations. Elements can be null if not needed. * @return The formatted citation. */ private String getAuthorYearParenthesisMarker(List<BibEntry> entries, Map<BibEntry, BibDatabase> database, String[] uniquifiers, int[] unlimAuthors) { String authorField = getStringCitProperty(AUTHOR_FIELD); // The bibtex field providing author names, e.g. "author" or "editor". int maxA = getIntCitProperty(MAX_AUTHORS); // The maximum number of authors to write out in full without using etal. Set to // -1 to always write out all authors. String yearSep = getStringCitProperty(YEAR_SEPARATOR); // The String to separate authors from year, e.g. "; ". String startBrace = getStringCitProperty(BRACKET_BEFORE); // The opening parenthesis. String endBrace = getStringCitProperty(BRACKET_AFTER); // The closing parenthesis. String citationSeparator = getStringCitProperty(CITATION_SEPARATOR); // The String to separate citations from each other. String yearField = getStringCitProperty(YEAR_FIELD); // The bibtex field providing the year, e.g. "year". String andString = getStringCitProperty(AUTHOR_LAST_SEPARATOR); // The String to add between the two last author names, e.g. " & ". StringBuilder sb = new StringBuilder(startBrace); for (int j = 0; j < entries.size(); j++) { BibEntry currentEntry = entries.get(j); // Check if this entry has been nulled due to grouping with the previous entry(ies): if (currentEntry == null) { continue; } if (j > 0) { sb.append(citationSeparator); } BibDatabase currentDatabase = database.get(currentEntry); int unlimA = unlimAuthors == null ? -1 : unlimAuthors[j]; int maxAuthors = unlimA > 0 ? unlimA : maxA; String author = getCitationMarkerField(currentEntry, currentDatabase, authorField); String authorString = createAuthorList(author, maxAuthors, andString, yearSep); sb.append(authorString); String year = getCitationMarkerField(currentEntry, currentDatabase, yearField); if (year != null) { sb.append(year); } if ((uniquifiers != null) && (uniquifiers[j] != null)) { sb.append(uniquifiers[j]); } } sb.append(endBrace); return sb.toString(); } /** * This method produces "Author (year)" style citation strings in many different forms. * * @param entries The list of BibEntry to get fields from. * @param database A map of BibEntry-BibDatabase pairs. * @param uniquefiers Optional parameters to separate similar citations. Can be null if not needed. * @return The formatted citation. */ private String getAuthorYearInTextMarker(List<BibEntry> entries, Map<BibEntry, BibDatabase> database, String[] uniquefiers, int[] unlimAuthors) { String authorField = getStringCitProperty(AUTHOR_FIELD); // The bibtex field providing author names, e.g. "author" or "editor". int maxA = getIntCitProperty(MAX_AUTHORS); // The maximum number of authors to write out in full without using etal. Set to // -1 to always write out all authors. String yearSep = getStringCitProperty(IN_TEXT_YEAR_SEPARATOR); // The String to separate authors from year, e.g. "; ". String startBrace = getStringCitProperty(BRACKET_BEFORE); // The opening parenthesis. String endBrace = getStringCitProperty(BRACKET_AFTER); // The closing parenthesis. String citationSeparator = getStringCitProperty(CITATION_SEPARATOR); // The String to separate citations from each other. String yearField = getStringCitProperty(YEAR_FIELD); // The bibtex field providing the year, e.g. "year". String andString = getStringCitProperty(AUTHOR_LAST_SEPARATOR_IN_TEXT); // The String to add between the two last author names, e.g. " & ". if (andString == null) { // Use the default one if no explicit separator for text is defined andString = getStringCitProperty(AUTHOR_LAST_SEPARATOR); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < entries.size(); i++) { BibEntry currentEntry = entries.get(i); // Check if this entry has been nulled due to grouping with the previous entry(ies): if (currentEntry == null) { continue; } BibDatabase currentDatabase = database.get(currentEntry); int unlimA = unlimAuthors == null ? -1 : unlimAuthors[i]; int maxAuthors = unlimA > 0 ? unlimA : maxA; if (i > 0) { sb.append(citationSeparator); } String author = getCitationMarkerField(currentEntry, currentDatabase, authorField); String authorString = createAuthorList(author, maxAuthors, andString, yearSep); sb.append(authorString); sb.append(startBrace); String year = getCitationMarkerField(currentEntry, currentDatabase, yearField); if (year != null) { sb.append(year); } if ((uniquefiers != null) && (uniquefiers[i] != null)) { sb.append(uniquefiers[i]); } sb.append(endBrace); } return sb.toString(); } /** * This method looks up a field for an entry in a database. Any number of backup fields can be used * if the primary field is empty. * * @param entry The entry. * @param database The database the entry belongs to. * @param field The field, or succession of fields, to look up. If backup fields are needed, separate * field names by /. E.g. to use "author" with "editor" as backup, specify FieldName.orFields(FieldName.AUTHOR, FieldName.EDITOR). * @return The resolved field content, or an empty string if the field(s) were empty. */ private String getCitationMarkerField(BibEntry entry, BibDatabase database, String field) { String authorField = getStringCitProperty(AUTHOR_FIELD); String[] fields = field.split(FieldName.FIELD_SEPARATOR); for (String s : fields) { Optional<String> content = entry.getResolvedFieldOrAlias(s, database); if ((content.isPresent()) && !content.get().trim().isEmpty()) { if (field.equals(authorField) && StringUtil.isInCurlyBrackets(content.get())) { return "{" + fieldFormatter.format(content.get()) + "}"; } return fieldFormatter.format(content.get()); } } // No luck? Return an empty string: return ""; } /** * Look up the nth author and return the proper last name for citation markers. * * @param al The author list. * @param number The number of the author to return. * @return The author name, or an empty String if inapplicable. */ private String getAuthorLastName(AuthorList al, int number) { StringBuilder sb = new StringBuilder(); if (al.getNumberOfAuthors() > number) { Author a = al.getAuthor(number); a.getVon().filter(von -> !von.isEmpty()).ifPresent(von -> sb.append(von).append(' ')); sb.append(a.getLast().orElse("")); } return sb.toString(); } /** * Take a finished citation and insert a string at the end (but inside the end bracket) * separated by "PageInfoSeparator" * * @param citation * @param pageInfo * @return */ public String insertPageInfo(String citation, String pageInfo) { String bracketAfter = getStringCitProperty(BRACKET_AFTER); if (citation.endsWith(bracketAfter)) { String first = citation.substring(0, citation.length() - bracketAfter.length()); return first + getStringCitProperty(PAGE_INFO_SEPARATOR) + pageInfo + bracketAfter; } else { return citation + getStringCitProperty(PAGE_INFO_SEPARATOR) + pageInfo; } } /** * Convenience method for checking the property for whether we use number citations or * author-year citations. * * @return true if we use numbered citations, false otherwise. */ public boolean isNumberEntries() { return (Boolean) getProperty(IS_NUMBER_ENTRIES); } /** * Convenience method for checking the property for whether we sort the bibliography * according to their order of appearance in the text. * * @return true to sort by appearance, false to sort alphabetically. */ public boolean isSortByPosition() { return (Boolean) getProperty(IS_SORT_BY_POSITION); } /** * Convenience method for checking whether citation markers should be italicized. * Will only be relevant if isFormatCitations() returns true. * * @return true to indicate that citations should be in italics. */ public boolean isItalicCitations() { return (Boolean) citProperties.get(ITALIC_CITATIONS); } /** * Convenience method for checking whether citation markers should be bold. * Will only be relevant if isFormatCitations() returns true. * * @return true to indicate that citations should be in bold. */ public boolean isBoldCitations() { return (Boolean) citProperties.get(BOLD_CITATIONS); } /** * Convenience method for checking whether citation markers formatted * according to the results of the isItalicCitations() and * isBoldCitations() methods. * * @return true to indicate that citations should be in italics. */ public boolean isFormatCitations() { return (Boolean) citProperties.get(FORMAT_CITATIONS); } public boolean isBibtexKeyCiteMarkers() { return (Boolean) citProperties.get(BIBTEX_KEY_CITATIONS); } /** * Get boolean property. * * @param key The property key * @return the value */ public boolean getBooleanCitProperty(String key) { return (Boolean) citProperties.get(key); } public int getIntCitProperty(String key) { return (Integer) citProperties.get(key); } public String getStringCitProperty(String key) { return (String) citProperties.get(key); } public String getCitationCharacterFormat() { return getStringCitProperty(CITATION_CHARACTER_FORMAT); } /** * Get a style property. * * @param propName The property name. * @return The property value, or null if it doesn't exist. */ public Object getProperty(String propName) { return properties.get(propName); } public boolean isFromResource() { return fromResource; } public String getLocalCopy() { return localCopy; } @Override public int compareTo(OOBibStyle other) { return getName().compareTo(other.getName()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o instanceof OOBibStyle) { OOBibStyle otherStyle = (OOBibStyle) o; return Objects.equals(path, otherStyle.path) && Objects.equals(name, otherStyle.name) && Objects.equals(citProperties, otherStyle.citProperties) && Objects.equals(properties, otherStyle.properties); } return false; } @Override public int hashCode() { return Objects.hash(path, name, citProperties, properties); } private String createAuthorList(String author, int maxAuthors, String andString, String yearSep) { Objects.requireNonNull(author); String etAlString = getStringCitProperty(ET_AL_STRING); // The String to represent authors that are not mentioned, e.g. " et al." String authorSep = getStringCitProperty(AUTHOR_SEPARATOR); // The String to add between author names except the last two, e.g. ", ". String oxfordComma = getStringCitProperty(OXFORD_COMMA); // The String to put after the second to last author in case of three or more authors StringBuilder sb = new StringBuilder(); AuthorList al = AuthorList.parse(author); if (!al.isEmpty()) { sb.append(getAuthorLastName(al, 0)); } if ((al.getNumberOfAuthors() > 1) && ((al.getNumberOfAuthors() <= maxAuthors) || (maxAuthors < 0))) { int j = 1; while (j < (al.getNumberOfAuthors() - 1)) { sb.append(authorSep); sb.append(getAuthorLastName(al, j)); j++; } if (al.getNumberOfAuthors() > 2) { sb.append(oxfordComma); } sb.append(andString); sb.append(getAuthorLastName(al, al.getNumberOfAuthors() - 1)); } else if (al.getNumberOfAuthors() > maxAuthors) { sb.append(etAlString); } sb.append(yearSep); return sb.toString(); } }