Java tutorial
/* Copyright (C) 2003-2015 JabRef contributors. 2003-2015 Ulrik Stervbo (ulriks AT ruc.dk) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package net.sf.jabref.logic.labelPattern; import java.util.ArrayList; import java.util.StringTokenizer; import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sf.jabref.logic.formatter.casechanger.Word; import net.sf.jabref.logic.util.strings.StringUtil; import net.sf.jabref.model.entry.AuthorList; import net.sf.jabref.model.database.BibDatabase; import net.sf.jabref.model.entry.BibEntry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import net.sf.jabref.*; import net.sf.jabref.exporter.layout.format.RemoveLatexCommands; import net.sf.jabref.util.Util; /** * This is the utility class of the LabelPattern package. */ public class LabelPatternUtil { // All single characters that we can use for extending a key to make it unique: private static final String CHARS = "abcdefghijklmnopqrstuvwxyz"; private static final Log LOGGER = LogFactory.getLog(LabelPatternUtil.class); public static List<String> DEFAULT_LABELPATTERN; static { LabelPatternUtil.updateDefaultPattern(); } private static BibDatabase database; public static void updateDefaultPattern() { LabelPatternUtil.DEFAULT_LABELPATTERN = LabelPatternUtil .split(JabRefPreferences.getInstance().get(JabRefPreferences.DEFAULT_LABEL_PATTERN)); } /** * Required for LabelPatternUtilTest * * @param db the DB to use as global database */ public static void setDataBase(BibDatabase db) { LabelPatternUtil.database = db; } private static String normalize(String content) { List<String> tokens = new ArrayList<>(); int b = 0; StringBuffer and = new StringBuffer(); StringBuffer token = new StringBuffer(); for (int p = 0; p < content.length(); p++) { if (b == 0) { String andString = and.toString(); // Avoid lots of calls if (((andString.isEmpty()) && (content.charAt(p) == ' ')) || (" ".equals(andString) && (content.charAt(p) == 'a')) || (" a".equals(andString) && (content.charAt(p) == 'n')) || (" an".equals(andString) && (content.charAt(p) == 'd'))) { and.append(content.charAt(p)); } else if (" and".equals(and.toString()) && (content.charAt(p) == ' ')) { and = new StringBuffer(); tokens.add(token.toString().trim()); token = new StringBuffer(); } else { if (content.charAt(p) == '{') { b++; } if (content.charAt(p) == '}') { b--; } token.append(and); and = new StringBuffer(); token.append(content.charAt(p)); } } else { token.append(content.charAt(p)); } } tokens.add(token.toString()); StringBuilder normalized = new StringBuilder(""); for (int i = 0; i < tokens.size(); i++) { if (i > 0) { normalized.append(" and "); } normalized.append(LabelPatternUtil.isInstitution(tokens.get(i)) ? LabelPatternUtil.generateInstitutionKey(tokens.get(i)) : LabelPatternUtil.removeDiacritics(tokens.get(i))); } return normalized.toString(); } /** * Will remove diacritics from the content. * * Replaces umlaut: \"x with xe, e.g. \"o -> oe, \"u -> ue, etc. * Removes all other diacritics: \?x -> x, e.g. \'a -> a, etc. * * @param content The content. * @return The content without diacritics. */ private static String removeDiacritics(String content) { if (content.isEmpty()) { return content; } // Replace umaut with '?e' content = content.replaceAll("\\{\\\\\"([a-zA-Z])\\}", "$1e"); content = content.replaceAll("\\\\\"\\{([a-zA-Z])\\}", "$1e"); content = content.replaceAll("\\\\\"([a-zA-Z])", "$1e"); // Remove diacritics content = content.replaceAll("\\{\\\\.([a-zA-Z])\\}", "$1"); content = content.replaceAll("\\\\.\\{([a-zA-Z])\\}", "$1"); content = content.replaceAll("\\\\.([a-zA-Z])", "$1"); return content; } /** * Unifies umlauts. * * Replaces: $\ddot{\mathrm{X}}$ (an alternative umlaut) with: {\"X} * Replaces: \?{X} and \?X with {\?X}, where ? is a diacritic symbol * * @param content The content. * @return The content with unified diacritics. */ private static String unifyDiacritics(String content) { return content.replaceAll("\\$\\\\ddot\\{\\\\mathrm\\{([^\\}])\\}\\}\\$", "{\\\"$1}") .replaceAll("(\\\\[^\\-a-zA-Z])\\{?([a-zA-Z])\\}?", "{$1$2}"); } /** * Check if a value is institution. * * This is usable for distinguishing between persons and institutions in * the author or editor fields. * * A person: * - "John Doe" * - "Doe, John" * * An institution: * - "{The Big Company or Institution Inc.}" * - "{The Big Company or Institution Inc. (BCI)}" * * @param author Author or editor. * @return True if the author or editor is an institution. */ private static boolean isInstitution(String author) { return StringUtil.isInCurlyBrackets(author); } /** * <p> * An author or editor may be and institution not a person. In that case the * key generator builds very long keys, e.g.: for “The Attributed * Graph Grammar System (AGG)” -> * “TheAttributedGraphGrammarSystemAGG”. * </p> * * <p> * An institution name should be inside <code>{}</code> brackets. If the * institution name also includes its abbreviation this abbreviation should * be also in <code>{}</code> brackets. For the previous example the value * should look like: * <code>{The Attributed Graph Grammar System ({AGG})}</code>. * </p> * * <p> * If an institution includes its abbreviation, i.e. "...({XYZ})", first * such abbreviation should be used as the key value part of such author. * </p> * * <p> * If an institution does not include its abbreviation the key should be * generated form its name in the following way: * </p> * * <p> * The institution value can contain: institution name, part of the * institution, address, etc. Those information should be separated by * comma. Name of the institution and possible part of the institution * should be on the beginning, while address and secondary information * should be on the end. * </p> * * Each part is examined separately: * <ol> * <li>We remove all tokens of a part which are one of the defined ignore * words (the, press), which end with a dot (ltd., co., ...) and which first * character is lowercase (of, on, di, ...).</li> * <li>We detect a type of the part: university, technology institute, * department, school, rest * <ul> * <li>University: <code>"Uni[NameOfTheUniversity]"</code></li> * <li>Department: will be an abbreviation of all words beginning with the * uppercase letter except of words: <code>d[ei]part.*</code>, school, * faculty</li> * <li>School: same as department</li> * <li>Rest: If there are less than 3 tokens in such part than the result * will be by concatenating those tokens, otherwise the result will be build * from the first letters of words starting with and uppercase letter.</li> * </ul> * </ol> * * Parts are concatenated together in the following way: * <ul> * <li>If there is a university part use it otherwise use the rest part.</li> * <li>If there is a school part append it.</li> * <li>If there is a department part and it is not same as school part * append it.</li> * </ul> * * Rest part is only the first part which do not match any other type. All * other parts (address, ...) are ignored. * * @param content the institution to generate a Bibtex key for * @return <ul> * <li>the institution key</li> * <li>"" in the case of a failure</li> * <li>null if content is null</li> * </ul> */ private static String generateInstitutionKey(String content) { if (content.isEmpty()) { return content; } content = LabelPatternUtil.unifyDiacritics(content); content = content.replaceAll("^\\{", "").replaceAll("\\}$", ""); Pattern regex = Pattern.compile(".*\\(\\{([A-Z]+)\\}\\).*"); Matcher matcher = regex.matcher(content); if (matcher.matches()) { return matcher.group(1); } content = LabelPatternUtil.removeDiacritics(content); String[] parts = content.split(","); // Key parts String university = null; String department = null; String school = null; String rest = null; List<String> ignore = Arrays.asList("press", "the"); for (int index = 0; index < parts.length; index++) { List<String> part = new ArrayList<>(); // Cleanup: remove unnecessary words. for (String k : parts[index].replaceAll("\\{[A-Z]+\\}", "").split("[ \\-_]")) { if ((!(k.isEmpty()) // remove empty && !ignore.contains(k.toLowerCase()) // remove ignored words && (k.charAt(k.length() - 1) != '.') && (String.valueOf(k.charAt(0))).matches("[A-Z]")) || ((k.length() >= 3) && "uni".equals(k.toLowerCase().substring(0, 2)))) { part.add(k); } } boolean isUniversity = false; // university boolean isTechnology = false; // technology institute boolean isDepartment = false; // departments boolean isSchool = false; // schools // Deciding about a part type... for (String k : part) { if ((k.length() >= 5) && "univ".equals(k.toLowerCase().substring(0, 5))) { isUniversity = true; } if ((k.length() >= 6) && "techn".equals(k.toLowerCase().substring(0, 6))) { isTechnology = true; } if ("school".equals(k.toLowerCase())) { isSchool = true; } if (((k.length() >= 7) && k.toLowerCase().substring(0, 7).matches("d[ei]part")) || ((k.length() >= 4) && "lab".equals(k.toLowerCase().substring(0, 4)))) { isDepartment = true; } } if (isTechnology) { isUniversity = false; // technology institute isn't university :-) } // University part looks like: Uni[NameOfTheUniversity] // // If university is detected than the previous part is suggested // as department if (isUniversity) { StringBuilder universitySB = new StringBuilder(); universitySB.append("Uni"); for (String k : part) { if ((k.length() >= 5) && !"univ".equals(k.toLowerCase().substring(0, 5))) { universitySB.append(k); } } university = universitySB.toString(); if ((index > 0) && (department == null)) { department = parts[index - 1]; } // School is an abbreviation of all the words beginning with a // capital letter excluding: department, school and faculty words. // // Explicitly defined department part is build the same way as // school } else if (isSchool || isDepartment) { StringBuilder schoolSB = new StringBuilder(); StringBuilder departmentSB = new StringBuilder(); for (String k : part) { if ((k.length() >= 7) && !k.toLowerCase().substring(0, 7).matches("d[ei]part") && !"school".equals(k.toLowerCase()) && !"faculty".equals(k.toLowerCase()) && !(k.replaceAll("[^A-Z]", "").isEmpty())) { if (isSchool) { schoolSB.append(k.replaceAll("[^A-Z]", "")); } if (isDepartment) { departmentSB.append(k.replaceAll("[^A-Z]", "")); } } } if (isSchool) { school = schoolSB.toString(); } if (isDepartment) { department = departmentSB.toString(); } // A part not matching university, department nor school. } else if (rest == null) { StringBuilder restSB = new StringBuilder(); // Less than 3 parts -> concatenate those if (part.size() < 3) { for (String k : part) { restSB.append(k); // More than 3 parts -> use 1st letter abbreviation } } else { for (String k : part) { k = k.replaceAll("[^A-Z]", ""); if (!(k.isEmpty())) { restSB.append(k); } } } rest = restSB.toString(); } } // Putting parts together. return (university == null ? rest : university) + (school == null ? "" : school) + ((department == null) || ((school != null) && department.equals(school)) ? "" : department); } /** * This method takes a string of the form [field1]spacer[field2]spacer[field3]..., * where the fields are the (required) fields of a BibTex entry. The string is split * into fields and spacers by recognizing the [ and ]. * * @param labelPattern a <code>String</code> * @return an <code>ArrayList</code> The first item of the list * is a string representation of the key pattern (the parameter), * the remaining items are the fields */ public static List<String> split(String labelPattern) { // A holder for fields of the entry to be used for the key List<String> fieldList = new ArrayList<>(); // Before we do anything, we add the parameter to the ArrayLIst fieldList.add(labelPattern); //String[] ss = labelPattern.split("\\[|\\]"); StringTokenizer tok = new StringTokenizer(labelPattern, "[]", true); while (tok.hasMoreTokens()) { fieldList.add(tok.nextToken()); } return fieldList; } /** * Generates a BibTeX label according to the pattern for a given entry type, and saves the unique label in the * <code>Bibtexentry</code>. * * The given database is used to avoid duplicate keys. * * @param dBase a <code>BibDatabase</code> * @param entry a <code>BibEntry</code> * @return modified BibEntry */ public static void makeLabel(MetaData metaData, BibDatabase dBase, BibEntry entry) { LabelPatternUtil.database = dBase; List<String> typeList; String key; StringBuilder stringBuilder = new StringBuilder(); boolean forceUpper = false; boolean forceLower = false; try { // get the type of entry String entryType = entry.getType().getName().toLowerCase(); // Get the arrayList corresponding to the type typeList = metaData.getLabelPattern().getValue(entryType); int typeListSize = typeList.size(); boolean field = false; for (int i = 1; i < typeListSize; i++) { String typeListEntry = typeList.get(i); if ("[".equals(typeListEntry)) { field = true; } else if ("]".equals(typeListEntry)) { field = false; } else if (field) { // check whether there is a modifier on the end such as // ":lower" // String modifier = null; String[] parts = LabelPatternUtil.parseFieldMarker(typeListEntry);//val.split(":"); String label = LabelPatternUtil.makeLabel(entry, parts[0]); // apply modifier if present if (parts.length > 1) { label = LabelPatternUtil.applyModifiers(label, parts, 1); } stringBuilder.append(label); } else { stringBuilder.append(typeListEntry); } } } catch (Exception e) { LOGGER.warn("Cannot make label", e); } // Remove all illegal characters from the key. key = Util.checkLegalKey(stringBuilder.toString()); // Remove Regular Expressions while generating Keys String regex = Globals.prefs.get(JabRefPreferences.KEY_PATTERN_REGEX); if ((regex != null) && !regex.trim().isEmpty()) { String replacement = Globals.prefs.get(JabRefPreferences.KEY_PATTERN_REPLACEMENT); key = key.replaceAll(regex, replacement); } if (forceUpper) { key = key.toUpperCase(); } if (forceLower) { key = key.toLowerCase(); } String oldKey = entry.getCiteKey(); int occurrences = LabelPatternUtil.database.getNumberOfKeyOccurrences(key); if ((oldKey != null) && oldKey.equals(key)) { occurrences--; // No change, so we can accept one dupe. } boolean alwaysAddLetter = Globals.prefs.getBoolean(JabRefPreferences.KEY_GEN_ALWAYS_ADD_LETTER); boolean firstLetterA = Globals.prefs.getBoolean(JabRefPreferences.KEY_GEN_FIRST_LETTER_A); if (!alwaysAddLetter && (occurrences == 0)) { // No dupes found, so we can just go ahead. if (!key.equals(oldKey)) { if (LabelPatternUtil.database.getEntryById(entry.getId()) == null) { // entry does not (yet) exist in the database, just update the entry entry.setField(BibEntry.KEY_FIELD, key); } else { LabelPatternUtil.database.setCiteKeyForEntry(entry.getId(), key); } } } else { // The key is already in use, so we must modify it. int number = 0; if (!alwaysAddLetter && !firstLetterA) { number = 1; } String moddedKey = key + LabelPatternUtil.getAddition(number); occurrences = LabelPatternUtil.database.getNumberOfKeyOccurrences(moddedKey); if ((oldKey != null) && oldKey.equals(moddedKey)) { occurrences--; } while (occurrences > 0) { number++; moddedKey = key + LabelPatternUtil.getAddition(number); occurrences = LabelPatternUtil.database.getNumberOfKeyOccurrences(moddedKey); if ((oldKey != null) && oldKey.equals(moddedKey)) { occurrences--; } } if (!moddedKey.equals(oldKey)) { if (LabelPatternUtil.database.getEntryById(entry.getId()) == null) { // entry does not (yet) exist in the database, just update the entry entry.setField(BibEntry.KEY_FIELD, moddedKey); } else { LabelPatternUtil.database.setCiteKeyForEntry(entry.getId(), moddedKey); } } } } /** * Applies modifiers to a label generated based on a field marker. * @param label The generated label. * @param parts String array containing the modifiers. * @param offset The number of initial items in the modifiers array to skip. * @return The modified label. */ public static String applyModifiers(String label, String[] parts, int offset) { if (parts.length > offset) { for (int j = offset; j < parts.length; j++) { String modifier = parts[j]; if ("lower".equals(modifier)) { label = label.toLowerCase(); } else if ("upper".equals(modifier)) { label = label.toUpperCase(); } else if ("abbr".equals(modifier)) { // Abbreviate - that is, StringBuilder abbreviateSB = new StringBuilder(); String[] words = label.replaceAll("[\\{\\}']", "").split("[\\(\\) \r\n\"]"); for (String word1 : words) { if (!word1.isEmpty()) { abbreviateSB.append(word1.charAt(0)); } } label = abbreviateSB.toString(); } else if (!modifier.isEmpty() && (modifier.charAt(0) == '(') && modifier.endsWith(")")) { // Alternate text modifier in parentheses. Should be inserted if // the label is empty: if (label.isEmpty() && (modifier.length() > 2)) { return modifier.substring(1, modifier.length() - 1); } } else { LOGGER.info("Key generator warning: unknown modifier '" + modifier + "'."); } } } return label; } public static String makeLabel(BibEntry entry, String val) { try { if (val.startsWith("auth") || val.startsWith("pureauth")) { /* * For label code "auth...": if there is no author, but there * are editor(s) (e.g. for an Edited Book), use the editor(s) * instead. (saw27@mrao.cam.ac.uk). This is what most people * want, but in case somebody really needs a field which expands * to nothing if there is no author (e.g. someone who uses both * "auth" and "ed" in the same label), we provide an alternative * form "pureauth..." which does not do this fallback * substitution of editor. */ String authString = entry.getField("author"); if (authString != null) { authString = LabelPatternUtil .normalize(LabelPatternUtil.database.resolveForStrings(authString)); } if (val.startsWith("pure")) { // remove the "pure" prefix so the remaining // code in this section functions correctly val = val.substring(4); } if ((authString == null) || authString.isEmpty()) { authString = entry.getField("editor"); if (authString == null) { authString = ""; } else { authString = LabelPatternUtil .normalize(LabelPatternUtil.database.resolveForStrings(authString)); } } // Gather all author-related checks, so we don't // have to check all the time. if ("auth".equals(val)) { return LabelPatternUtil.firstAuthor(authString); } else if ("authForeIni".equals(val)) { return LabelPatternUtil.firstAuthorForenameInitials(authString); } else if ("authFirstFull".equals(val)) { return LabelPatternUtil.firstAuthorVonAndLast(authString); } else if ("authors".equals(val)) { return LabelPatternUtil.allAuthors(authString); } else if ("authorsAlpha".equals(val)) { return LabelPatternUtil.authorsAlpha(authString); } // Last author's last name else if ("authorLast".equals(val)) { return LabelPatternUtil.lastAuthor(authString); } else if ("authorLastForeIni".equals(val)) { return LabelPatternUtil.lastAuthorForenameInitials(authString); } else if ("authorIni".equals(val)) { return LabelPatternUtil.oneAuthorPlusIni(authString); } else if (val.matches("authIni[\\d]+")) { int num = Integer.parseInt(val.substring(7)); String s = LabelPatternUtil.authIniN(authString, num); return s == null ? "" : s; } else if ("auth.auth.ea".equals(val)) { String s = LabelPatternUtil.authAuthEa(authString); return s == null ? "" : s; } else if ("auth.etal".equals(val)) { String s = LabelPatternUtil.authEtal(authString, ".", ".etal"); return s == null ? "" : s; } else if ("authEtAl".equals(val)) { String s = LabelPatternUtil.authEtal(authString, "", "EtAl"); return s == null ? "" : s; } else if ("authshort".equals(val)) { String s = LabelPatternUtil.authshort(authString); return s == null ? "" : s; } else if (val.matches("auth[\\d]+_[\\d]+")) { String[] nums = val.substring(4).split("_"); String s = LabelPatternUtil.authN_M(authString, Integer.parseInt(nums[0]), Integer.parseInt(nums[1])); return s == null ? "" : s; } else if (val.matches("auth\\d+")) { // authN. First N chars of the first author's last // name. String fa = LabelPatternUtil.firstAuthor(authString); if (fa == null) { return ""; } int num = Integer.parseInt(val.substring(4)); if (num > fa.length()) { num = fa.length(); } return fa.substring(0, num); } else if (val.matches("authors\\d+")) { String s = LabelPatternUtil.NAuthors(authString, Integer.parseInt(val.substring(7))); return s == null ? "" : s; } else { // This "auth" business was a dead end, so just // use it literally: return LabelPatternUtil.getField(entry, val); } } else if (val.startsWith("ed")) { // Gather all markers starting with "ed" here, so we // don't have to check all the time. if ("edtr".equals(val)) { return LabelPatternUtil.firstAuthor(entry.getField("editor")); } else if ("edtrForeIni".equals(val)) { return LabelPatternUtil.firstAuthorForenameInitials(entry.getField("editor")); } else if ("editors".equals(val)) { return LabelPatternUtil.allAuthors(entry.getField("editor")); // Last author's last name } else if ("editorLast".equals(val)) { return LabelPatternUtil.lastAuthor(entry.getField("editor")); } else if ("editorLastForeIni".equals(val)) { return LabelPatternUtil.lastAuthorForenameInitials(entry.getField("editor")); } else if ("editorIni".equals(val)) { return LabelPatternUtil.oneAuthorPlusIni(entry.getField("editor")); } else if (val.matches("edtrIni[\\d]+")) { int num = Integer.parseInt(val.substring(7)); String s = LabelPatternUtil.authIniN(entry.getField("editor"), num); return s == null ? "" : s; } else if (val.matches("edtr[\\d]+_[\\d]+")) { String[] nums = val.substring(4).split("_"); String s = LabelPatternUtil.authN_M(entry.getField("editor"), Integer.parseInt(nums[0]), Integer.parseInt(nums[1]) - 1); return s == null ? "" : s; } else if ("edtr.edtr.ea".equals(val)) { String s = LabelPatternUtil.authAuthEa(entry.getField("editor")); return s == null ? "" : s; } else if ("edtrshort".equals(val)) { String s = LabelPatternUtil.authshort(entry.getField("editor")); return s == null ? "" : s; } // authN. First N chars of the first author's last // name. else if (val.matches("edtr\\d+")) { String fa = LabelPatternUtil.firstAuthor(entry.getField("editor")); if (fa == null) { return ""; } int num = Integer.parseInt(val.substring(4)); if (num > fa.length()) { num = fa.length(); } return fa.substring(0, num); } else { // This "ed" business was a dead end, so just // use it literally: return LabelPatternUtil.getField(entry, val); } } else if ("firstpage".equals(val)) { return LabelPatternUtil.firstPage(entry.getField("pages")); } else if ("lastpage".equals(val)) { return LabelPatternUtil.lastPage(entry.getField("pages")); } else if ("shorttitle".equals(val)) { return LabelPatternUtil.getTitleWords(3, entry.getField("title")); } else if ("veryshorttitle".equals(val)) { return LabelPatternUtil.getTitleWords(1, entry.getField("title")); } else if ("shortyear".equals(val)) { String ss = entry.getFieldOrAlias("year"); if (ss == null) { return ""; } else if (ss.startsWith("in") || ss.startsWith("sub")) { return "IP"; } else if (ss.length() > 2) { return ss.substring(ss.length() - 2); } else { return ss; } } else if (val.matches("keyword\\d+")) { // according to LabelPattern.php, it returns keyword number n int num = Integer.parseInt(val.substring(7)); List<String> separatedKeywords = entry.getSeparatedKeywords(); if (separatedKeywords.size() < num) { // not enough keywords return ""; } else { // num counts from 1 to n, but index in arrayList count from 0 to n-1 return separatedKeywords.get(num - 1); } } else if (val.matches("keywords\\d*")) { // return all keywords, not separated int num; if (val.length() > 8) { num = Integer.parseInt(val.substring(8)); } else { num = Integer.MAX_VALUE; } List<String> separatedKeywords = entry.getSeparatedKeywords(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < Math.min(separatedKeywords.size(), num); i++) { String keyword = separatedKeywords.get(i); // remove all spaces keyword = keyword.replaceAll("\\s+", ""); sb.append(keyword); } return sb.toString(); } else { // we haven't seen any special demands return LabelPatternUtil.getField(entry, val); } } catch (NullPointerException ex) { return ""; } } /** * Look up a field of a BibEntry, returning its String value, or an * empty string if it isn't set. * @param entry The entry. * @param field The field to look up. * @return The field value. */ private static String getField(BibEntry entry, String field) { String s = entry.getFieldOrAlias(field); return s == null ? "" : s; } /** * Computes an appendix to a BibTeX key that could make it unique. We use * a-z for numbers 0-25, and then aa-az, ba-bz, etc. * * @param number * The appendix number. * @return The String to append. */ private static String getAddition(int number) { if (number >= LabelPatternUtil.CHARS.length()) { int lastChar = number % LabelPatternUtil.CHARS.length(); return LabelPatternUtil.getAddition((number / LabelPatternUtil.CHARS.length()) - 1) + LabelPatternUtil.CHARS.substring(lastChar, lastChar + 1); } else { return LabelPatternUtil.CHARS.substring(number, number + 1); } } /** * Determines "number" words out of the "title" field in the given BibTeX entry */ static String getTitleWords(int number, String title) { String ss = new RemoveLatexCommands().format(title); StringBuilder stringBuilder = new StringBuilder(); StringBuilder current; int piv = 0; int words = 0; // sorry for being English-centric. I guess these // words should really be an editable preference. mainl: while ((piv < ss.length()) && (words < number)) { current = new StringBuilder(); // Get the next word: while ((piv < ss.length()) && !Character.isWhitespace(ss.charAt(piv)) && (ss.charAt(piv) != '-')) { current.append(ss.charAt(piv)); piv++; } piv++; // Check if it is ok: String word = current.toString().trim(); if (word.isEmpty()) { continue; } for (String smallWord : Word.SMALLER_WORDS) { if (word.equalsIgnoreCase(smallWord)) { continue mainl; } } // If we get here, the word was accepted. if (stringBuilder.length() > 0) { stringBuilder.append(' '); } stringBuilder.append(word); words++; } return LabelPatternUtil.keepLettersAndDigitsOnly(stringBuilder.toString()); } private static String keepLettersAndDigitsOnly(String in) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < in.length(); i++) { if (Character.isLetterOrDigit(in.charAt(i))) { stringBuilder.append(in.charAt(i)); } } return stringBuilder.toString(); } /** * Gets the last name of the first author/editor * * @param authorField * a <code>String</code> * @return the surname of an author/editor or "" if no author was found * This method is guaranteed to never return null. * * @throws NullPointerException * if authorField == null */ static String firstAuthor(String authorField) { AuthorList authorList = AuthorList.getAuthorList(authorField); if (authorList.size() == 0) { return ""; } String s = authorList.getAuthor(0).getLast(); return s == null ? "" : s; } /** * Gets the first name initials of the first author/editor * * @param authorField * a <code>String</code> * @return the first name initial of an author/editor or "" if no author was found * This method is guaranteed to never return null. * * @throws NullPointerException * if authorField == null */ static String firstAuthorForenameInitials(String authorField) { AuthorList authorList = AuthorList.getAuthorList(authorField); if (authorList.size() == 0) { return ""; } String s = authorList.getAuthor(0).getFirstAbbr(); return s == null ? "" : s.substring(0, 1); } /** * Gets the von part and the last name of the first author/editor * No spaces are returned * * @param authorField * a <code>String</code> * @return the von part and surname of an author/editor or "" if no author was found. * This method is guaranteed to never return null. * * @throws NullPointerException * if authorField == null */ static String firstAuthorVonAndLast(String authorField) { AuthorList authorList = AuthorList.getAuthorList(authorField); if (authorList.size() == 0) { return ""; } String vonAuthor = authorList.getAuthor(0).getVon().replaceAll(" ", ""); StringBuilder stringBuilder = new StringBuilder(); if (vonAuthor != null) { stringBuilder.append(vonAuthor); } vonAuthor = authorList.getAuthor(0).getLast(); if (vonAuthor != null) { stringBuilder.append(vonAuthor); } return stringBuilder.toString(); } /** * Gets the last name of the last author/editor * @param authorField a <code>String</code> * @return the surname of an author/editor */ static String lastAuthor(String authorField) { String[] tokens = AuthorList.fixAuthorForAlphabetization(authorField).split("\\s+\\band\\b\\s+"); if (tokens.length > 0) { String[] lastAuthor = tokens[tokens.length - 1].split(","); return lastAuthor[0]; } else { // if author is empty return ""; } } /** * Gets the forename initials of the last author/editor * * @param authorField * a <code>String</code> * @return the forename initial of an author/editor or "" if no author was found * This method is guaranteed to never return null. * * @throws NullPointerException * if authorField == null */ static String lastAuthorForenameInitials(String authorField) { AuthorList authorList = AuthorList.getAuthorList(authorField); if (authorList.size() == 0) { return ""; } String s = authorList.getAuthor(authorList.size() - 1).getFirstAbbr(); return s == null ? "" : s.substring(0, 1); } /** * Gets the last name of all authors/editors * @param authorField a <code>String</code> * @return the sur name of all authors/editors */ static String allAuthors(String authorField) { // Quick hack to use NAuthors to avoid code duplication return NAuthors(authorField, Integer.MAX_VALUE); } /** * Returns the authors according to the BibTeX-alpha-Style * @param authorField string containing the value of the author field * @return the initials of all authornames */ static String authorsAlpha(String authorField) { String authors = ""; String fixedAuthors = AuthorList.fixAuthor_lastNameOnlyCommas(authorField, false); // drop the "and" before the last author // -> makes processing easier fixedAuthors = fixedAuthors.replace(" and ", ", "); String[] tokens = fixedAuthors.split(","); int max = tokens.length > 4 ? 3 : tokens.length; if (max == 1) { String[] firstAuthor = tokens[0].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++) { authors = authors.concat(firstAuthor[j].substring(0, 1)); } // append last part of last name completely authors = authors.concat(firstAuthor[firstAuthor.length - 1].substring(0, Math.min(3, firstAuthor[firstAuthor.length - 1].length()))); } else { for (int i = 0; i < max; i++) { // replace all whitespaces by " " // split the lastname at " " String[] curAuthor = tokens[i].replaceAll("\\s+", " ").trim().split(" "); for (String aCurAuthor : curAuthor) { // use first character of each part of lastname authors = authors.concat(aCurAuthor.substring(0, 1)); } } if (tokens.length > 4) { authors = authors.concat("+"); } } return authors; } /** * Gets the surnames of the first N authors and appends EtAl if there are more than N authors * @param authorField a <code>String</code> * @param n the number of desired authors * @return Gets the surnames of the first N authors and appends EtAl if there are more than N authors */ static String NAuthors(String authorField, int n) { String[] tokens = AuthorList.fixAuthorForAlphabetization(authorField).split("\\s+\\band\\b\\s+"); int i = 0; StringBuilder authorSB = new StringBuilder(); while ((tokens.length > i) && (i < n)) { String lastName = tokens[i].replaceAll(",\\s+.*", ""); authorSB.append(lastName); i++; } if (tokens.length > n) { authorSB.append("EtAl"); } return authorSB.toString(); } /** * Gets the first part of the last name of the first * author/editor, and appends the last name initial of the * remaining authors/editors. * Maximum 5 characters * @param authorField a <code>String</code> * @return the surname of all authors/editors */ static String oneAuthorPlusIni(String authorField) { final int CHARS_OF_FIRST = 5; authorField = AuthorList.fixAuthorForAlphabetization(authorField); String[] tokens = authorField.split("\\s+\\band\\b\\s+"); if (tokens.length == 0) { return ""; } String firstAuthor = tokens[0].split(",")[0]; StringBuilder authorSB = new StringBuilder(); authorSB.append(firstAuthor.substring(0, Math.min(CHARS_OF_FIRST, firstAuthor.length()))); int i = 1; while (tokens.length > i) { // convert lastname, firstname to firstname lastname authorSB.append(tokens[i].charAt(0)); i++; } return authorSB.toString(); } /** * auth.auth.ea format: * Isaac Newton and James Maxwell and Albert Einstein (1960) * Isaac Newton and James Maxwell (1960) * give: * Newton.Maxwell.ea * Newton.Maxwell */ static String authAuthEa(String authorField) { authorField = AuthorList.fixAuthorForAlphabetization(authorField); String[] tokens = authorField.split("\\s+\\band\\b\\s+"); if (tokens.length == 0) { return ""; } StringBuilder author = new StringBuilder(); // append first author author.append((tokens[0].split(","))[0]); if (tokens.length >= 2) { // append second author author.append('.').append((tokens[1].split(","))[0]); } if (tokens.length > 2) { // append ".ea" if more than 2 authors author.append(".ea"); } return author.toString(); } /** * auth.etal, authEtAl, ... format: * Isaac Newton and James Maxwell and Albert Einstein (1960) * Isaac Newton and James Maxwell (1960) * * auth.etal give (delim=".", append=".etal"): * Newton.etal * Newton.Maxwell * * authEtAl give (delim="", append="EtAl"): * NewtonEtAl * NewtonMaxwell * * Note that [authEtAl] equals [authors2] */ static String authEtal(String authorField, String delim, String append) { authorField = AuthorList.fixAuthorForAlphabetization(authorField); String[] tokens = authorField.split("\\s*\\band\\b\\s*"); if (tokens.length == 0) { return ""; } StringBuilder author = new StringBuilder(); author.append((tokens[0].split(","))[0]); if (tokens.length == 2) { author.append(delim).append((tokens[1].split(","))[0]); } else if (tokens.length > 2) { author.append(append); } return author.toString(); } /** * The first N characters of the Mth author/editor. * M starts counting from 1 */ static String authN_M(String authorField, int n, int m) { // have m counting from 0 m--; authorField = AuthorList.fixAuthorForAlphabetization(authorField); String[] tokens = authorField.split("\\s+\\band\\b\\s+"); if ((tokens.length <= m) || (n < 0) || (m < 0)) { return ""; } String lastName = (tokens[m].split(","))[0]; if (lastName.length() <= n) { return lastName; } else { return lastName.substring(0, n); } } /** * authshort format: * added by Kolja Brix, kbx@users.sourceforge.net * * given author names * * Isaac Newton and James Maxwell and Albert Einstein and N. Bohr * * Isaac Newton and James Maxwell and Albert Einstein * * Isaac Newton and James Maxwell * * Isaac Newton * * yield * * NME+ * * NME * * NM * * Newton */ static String authshort(String authorField) { authorField = AuthorList.fixAuthorForAlphabetization(authorField); StringBuilder author = new StringBuilder(); String[] tokens = authorField.split("\\band\\b"); int i = 0; if (tokens.length == 1) { author.append(LabelPatternUtil.authN_M(authorField, authorField.length(), 1)); } else if (tokens.length >= 2) { while ((tokens.length > i) && (i < 3)) { author.append(LabelPatternUtil.authN_M(authorField, 1, i + 1)); i++; } if (tokens.length > 3) { author.append('+'); } } return author.toString(); } /** * authIniN format: * * Each author gets (N div #authors) chars, the remaining (N mod #authors) * chars are equally distributed to the authors first in the row. * * If (N < #authors), only the first N authors get mentioned. * * For example if * * a) I. Newton and J. Maxwell and A. Einstein and N. Bohr (..) * * b) I. Newton and J. Maxwell and A. Einstein * * c) I. Newton and J. Maxwell * * d) I. Newton * * authIni4 gives: a) NMEB, b) NeME, c) NeMa, d) Newt * * @param authorField * The authors to format. * * @param n * The maximum number of characters this string will be long. A * negative number or zero will lead to "" be returned. * * @throws NullPointerException * if authorField is null and n > 0 */ static String authIniN(String authorField, int n) { if (n <= 0) { return ""; } authorField = AuthorList.fixAuthorForAlphabetization(authorField); StringBuilder author = new StringBuilder(); String[] tokens = authorField.split("\\band\\b"); if (tokens.length == 0) { return author.toString(); } int i = 0; int charsAll = n / tokens.length; while (tokens.length > i) { if (i < (n % tokens.length)) { author.append(LabelPatternUtil.authN_M(authorField, charsAll + 1, i + 1)); } else { author.append(LabelPatternUtil.authN_M(authorField, charsAll, i + 1)); } i++; } if (author.length() <= n) { return author.toString(); } else { return author.toString().substring(0, n); } } /** * Split the pages field into separate numbers and return the lowest * * @param pages * (may not be null) a pages string such as 42--111 or * 7,41,73--97 or 43+ * * @return the first page number or "" if no number is found in the string * * @throws NullPointerException * if pages is null */ public static String firstPage(String pages) { final String[] _pages = pages.split("\\D+"); int result = Integer.MAX_VALUE; for (String n : _pages) { if (n.matches("\\d+")) { result = Math.min(Integer.parseInt(n), result); } } if (result == Integer.MAX_VALUE) { return ""; } else { return String.valueOf(result); } } /** * Split the pages field into separate numbers and return the highest * * @param pages * a pages string such as 42--111 or 7,41,73--97 or 43+ * * @return the first page number or "" if no number is found in the string * * @throws NullPointerException * if pages is null. */ public static String lastPage(String pages) { final String[] _pages = pages.split("\\D+"); int result = Integer.MIN_VALUE; for (String n : _pages) { if (n.matches("\\d+")) { result = Math.max(Integer.parseInt(n), result); } } if (result == Integer.MIN_VALUE) { return ""; } else { return String.valueOf(result); } } /** * Parse a field marker with modifiers, possibly containing a parenthesised modifier, * as well as escaped colons and parentheses. * @param arg The argument string. * @return An array of strings representing the parts of the marker */ private static String[] parseFieldMarker(String arg) { List<String> parts = new ArrayList<>(); StringBuilder current = new StringBuilder(); boolean escaped = false; int inParenthesis = 0; for (int i = 0; i < arg.length(); i++) { if ((arg.charAt(i) == ':') && !escaped && (inParenthesis == 0)) { parts.add(current.toString()); current = new StringBuilder(); } else if ((arg.charAt(i) == '(') && !escaped) { inParenthesis++; current.append(arg.charAt(i)); } else if ((arg.charAt(i) == ')') && !escaped && (inParenthesis > 0)) { inParenthesis--; current.append(arg.charAt(i)); } else if (arg.charAt(i) == '\\') { if (escaped) { escaped = false; current.append(arg.charAt(i)); } else { escaped = true; } } else if (escaped) { current.append(arg.charAt(i)); escaped = false; } else { current.append(arg.charAt(i)); } } parts.add(current.toString()); return parts.toArray(new String[parts.size()]); } }