package net.sf.jabref.bst; import net.sf.jabref.AuthorList; import net.sf.jabref.AuthorList.Author; /** * From Bibtex: * * "The |built_in| function {\.{format.name\$}} pops the * top three literals (they are a string, an integer, and a string * literal, in that order). The last string literal represents a * name list (each name corresponding to a person), the integer * literal specifies which name to pick from this list, and the * first string literal specifies how to format this name, as * described in the \BibTeX\ documentation. Finally, this function * pushes the formatted name. If any of the types is incorrect, it * complains and pushes the null string." * * Sounds easy - is a nightmare... X-( * */ public class BibtexNameFormatter { public static String formatName(String authorsNameList, int whichName, String formatString, Warn warn){ AuthorList al = AuthorList.getAuthorList(authorsNameList); if (whichName < 1 && whichName > al.size()){ warn.warn("AuthorList " + authorsNameList + " does not contain an author with number " + whichName); return ""; } return formatName(al.getAuthor(whichName-1), formatString, warn); } /** * * @param author * @param format * @param warn may-be-null * @return */ public static String formatName(Author author, String format, Warn warn) { StringBuffer sb = new StringBuffer(); char[] c = format.toCharArray(); int n = c.length; int braceLevel = 0; int group = 0; int i = 0; while (i < n){ if (c[i] == '{'){ group++; int groupStart = sb.length(); i++; braceLevel++; StringBuffer level1Chars = new StringBuffer(); StringBuffer wholeChar = new StringBuffer(); while (i < n && braceLevel > 0){ wholeChar.append(c[i]); if (c[i] == '{'){ braceLevel++; i++; continue; } if (c[i] == '}'){ braceLevel--; i++; continue; } if (braceLevel == 1){ if (Character.isLetter(c[i])){ if ("fvlj".indexOf(c[i]) == -1){ if (warn != null) warn.warn("Format String in format.name$ may only contain fvlj on brace level 1 in group " + group + ": " + format); } else level1Chars.append(c[i]); } } i++; } i--; // unskip last brace (for last i++ at the end) String control = level1Chars.toString().toLowerCase(); if (control.length() == 0) continue; if (control.length() > 2 && warn != null) warn.warn("Format String in format.name$ may only be one or two character long on brace level 1 in group " + group + ": " + format); char type = control.charAt(0); String tokenS; switch(type){ case 'f': tokenS = author.getFirst(); break; case 'v': tokenS = author.getVon(); break; case 'l': tokenS = author.getLast(); break; case 'j': tokenS = author.getJr(); break; default: throw new VMException("Internal error"); } if (tokenS == null){ i++; continue; } String[] tokens = tokenS.split(" "); boolean abbreviateThatIsSingleLetter = true; if (control.length() == 2){ if (control.charAt(1) == control.charAt(0)){ abbreviateThatIsSingleLetter = false; } else { if (warn != null) warn.warn("Format String in format.name$ may only contain one type of vlfj on brace level 1 in group " + group + ": " + format); } } // Now we know what to do if (braceLevel == 0 && wholeChar.charAt(wholeChar.length() - 1) == '}'){ wholeChar.deleteCharAt(wholeChar.length() - 1); } char[] d = wholeChar.toString().toCharArray(); int bLevel = 1; String interToken = null; for (int j = 0; j < d.length; j++){ if (Character.isLetter(d[j]) && bLevel == 1){ groupStart = sb.length(); if (!abbreviateThatIsSingleLetter){ j++; } if (j+1 < d.length){ if (d[j+1] == '{'){ StringBuffer interTokenSb = new StringBuffer(); j = consumeToMatchingBrace(interTokenSb, d, j+1); interToken = interTokenSb.substring(1,interTokenSb.length()-1); } } for (int k = 0; k < tokens.length; k++){ String token = tokens[k]; if (abbreviateThatIsSingleLetter){ String[] dashes = token.split("-"); StringBuffer abbToken = new StringBuffer(); for (int t = 0; t < dashes.length - 1; t++){ abbToken.append(getFirstCharOfString(dashes[t])).append(".-"); } if (dashes.length > 0) abbToken.append(getFirstCharOfString(dashes[dashes.length - 1])); token = abbToken.toString(); } // Output token sb.append(token); if (k < tokens.length - 1){ // Output Intertoken String if (interToken == null){ if (abbreviateThatIsSingleLetter){ sb.append("."); } // No clue what this means (What the hell are tokens anyway??? // if (lex_class[name_sep_char[cur_token]] = sep_char) then // append_ex_buf_char_and_check (name_sep_char[cur_token]) if (k == tokens.length - 2 || numberOfChars(sb.substring(groupStart, sb.length()), 3) < 3){ sb.append("~"); } else { sb.append(" "); } } else { sb.append(interToken); } } } } else if (d[j] == '}'){ bLevel--; if (bLevel > 0){ sb.append('}'); } } else if (d[j] == '{'){ bLevel++; sb.append('{'); } else { sb.append(d[j]); } } if (sb.length() > 0){ boolean noDisTie = false; if (sb.charAt(sb.length() - 1) == '~' && (numberOfChars(sb.substring(groupStart, sb.length()), 4) >= 4 || (sb.length() > 1 && (noDisTie = sb.charAt(sb.length() - 2) == '~')))){ sb.deleteCharAt(sb.length() - 1); if (!noDisTie) sb.append(' '); } } } else if (c[i] == '}'){ if (warn != null) warn.warn("Unmatched brace in format string: " + format); } else { sb.append(c[i]); // verbatim } i++; } if (braceLevel != 0) warn.warn("Unbalanced brace in format string for nameFormat: " + format); return sb.toString(); } /** * Including the matching brace. * * @param sb * @param c * @param pos * @return * * assert c[pos] == '{' */ public static int consumeToMatchingBrace(StringBuffer sb, char[] c, int pos){ int braceLevel = 0; // assert c[pos] == '{'; for (int i = pos; i < c.length; i++){ if (c[i] == '}'){ braceLevel--; if (braceLevel == 0){ sb.append('}'); return i; } } else if (c[i] == '{'){ braceLevel++; } sb.append(c[i]); } return c.length; } /** * Takes care of special characters too * * @param s * @return */ public static String getFirstCharOfString(String s){ char[] c = s.toCharArray(); for (int i = 0; i < c.length; i++){ if (Character.isLetter(c[i])){ return String.valueOf(c[i]); } if (c[i] == '{'){ if (i+1 < c.length && c[i+1] == '\\'){ StringBuffer sb = new StringBuffer(); consumeToMatchingBrace(sb, c, i); return sb.toString(); } } } return ""; } public static int numberOfChars(String token, int stop) { if (stop < 0) stop = Integer.MAX_VALUE; int result = 0; int i = 0; char[] c = token.toCharArray(); int n = c.length; int braceLevel = 0; while (i < n && result < stop){ i++; if (c[i-1] == '{'){ braceLevel++; if (braceLevel == 1 && i < n && c[i] == '\\'){ i++; while (i < n && braceLevel > 0){ if (c[i] == '}'){ braceLevel--; } else if (c[i] == '{'){ braceLevel++; } i++; } } } else if (c[i-1] == '}'){ braceLevel--; } result++; } return result; } }