/* * Created on 13-Dec-2003 */ package net.sf.jabref.labelPattern; import java.util.ArrayList; import java.util.StringTokenizer; import java.util.Arrays; import java.util.List; import net.sf.jabref.*; import net.sf.jabref.export.layout.format.RemoveLatexCommands; /** * * @author Ulrik Stervbo (ulriks AT ruc.dk) */ /** * This is the utility class of the LabelPattern package. * @author Ulrik Stervbo (ulriks AT ruc.dk) */ public class LabelPatternUtil { // All single characters that we can use for extending a key to make it unique: private static String CHARS = "abcdefghijklmnopqrstuvwxyz"; public static ArrayList<String> DEFAULT_LABELPATTERN; static { updateDefaultPattern(); } private static BibtexDatabase _db; public static void updateDefaultPattern() { DEFAULT_LABELPATTERN = split(JabRefPreferences.getInstance().get("defaultLabelPattern")); } /** * 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 second item is the spacer character (a <code>String</code>). */ public static ArrayList<String> split(String labelPattern) { // A holder for fields of the entry to be used for the key ArrayList<String> _alist = new ArrayList<String>(); // Before we do anything, we add the parameter to the ArrayLIst _alist.add(labelPattern); //String[] ss = labelPattern.split("\\[|\\]"); StringTokenizer tok = new StringTokenizer(labelPattern, "[]", true); while (tok.hasMoreTokens()) { _alist.add(tok.nextToken()); } return _alist; /* // Regular expresion for identifying the fields Pattern pi = Pattern.compile("\\[\\w*\\]"); // Regular expresion for identifying the spacer Pattern ps = Pattern.compile("\\].()*\\["); // The matcher for the field Matcher mi = pi.matcher(labelPattern); // The matcher for the spacer char Matcher ms = ps.matcher(labelPattern); // Before we do anything, we add the parameter to the ArrayLIst _alist.add(labelPattern); // If we can find the spacer character if(ms.find()){ String t_spacer = ms.group(); // Remove the `]' and `[' at the ends // We cant imagine a spacer of omre than one character. t_spacer = t_spacer.substring(1,2); _alist.add(t_spacer); } while(mi.find()){ // Get the matched string String t_str = mi.group(); int _sindex = 1; int _eindex = t_str.length() -1; // Remove the `[' and `]' at the ends t_str = t_str.substring(_sindex, _eindex); _alist.add(t_str); } return _alist;*/ } /** * Generates a BibTeX label according to the pattern for a given entry type, and * returns the <code>Bibtexentry</code> with the unique label. * @param table a <code>LabelPattern</code> * @param database a <code>BibtexDatabase</code> * @param _entry a <code>BibtexEntry</code> * @return modified Bibtexentry */ public static BibtexEntry makeLabel(LabelPattern table, BibtexDatabase database, BibtexEntry _entry) { _db = database; ArrayList<String> _al; String _label; StringBuffer _sb = new StringBuffer(); boolean forceUpper = false, forceLower = false; try { // get the type of entry String _type = _entry.getType().getName().toLowerCase(); // Get the arrayList corrosponding to the type _al = table.getValue(_type); int _alSize = _al.size(); boolean field = false; for (int i = 1; i < _alSize; i++) { String val = _al.get(i).toString(); if (val.equals("[")) { field = true; } else if (val.equals("]")) { field = false; } else if (field) { /* * Edited by Seb Wills <saw27@mrao.cam.ac.uk> on 13-Apr-2004 * Added new pseudo-fields "shortyear" and "veryshorttitle", * and and ":lower" modifier for all fields (in a way easily * extended to other modifiers). Helpfile * help/LabelPatterns.html updated accordingly. */ // check whether there is a modifier on the end such as // ":lower" // String modifier = null; String[] parts = parseFieldMarker(val);//val.split(":"); String label = makeLabel(_entry, parts[0]); // apply modifier if present if (parts.length > 1) label = applyModifiers(label, parts, 1); _sb.append(label); } else { _sb.append(val); } } } catch (Exception e) { System.err.println(e); } // Remove all illegal characters from the key. _label = Util.checkLegalKey(_sb.toString()); // Patch by Toralf Senger: // Remove Regular Expressions while generating Keys String regex = Globals.prefs.get("KeyPatternRegex"); if ((regex != null) && (regex.trim().length() > 0)) { String replacement = Globals.prefs.get("KeyPatternReplacement"); _label = _label.replaceAll(regex, replacement); } if (forceUpper) { _label = _label.toUpperCase(); } if (forceLower) { _label = _label.toLowerCase(); } String oldKey = _entry.getCiteKey(); int occurences = _db.getNumberOfKeyOccurences(_label); if ((oldKey != null) && oldKey.equals(_label)) occurences--; // No change, so we can accept one dupe. boolean alwaysAddLetter = Globals.prefs.getBoolean("keyGenAlwaysAddLetter"), firstLetterA = Globals.prefs.getBoolean("keyGenFirstLetterA"); if (!alwaysAddLetter && (occurences == 0)) { // No dupes found, so we can just go ahead. if (!_label.equals(oldKey)) _db.setCiteKeyForEntry(_entry.getId(), _label); } else { // The key is already in use, so we must modify it. int number = 0; if (!alwaysAddLetter && !firstLetterA) number = 1; String moddedKey = _label + getAddition(number); occurences = _db.getNumberOfKeyOccurences(moddedKey); if ((oldKey != null) && oldKey.equals(moddedKey)) occurences--; while (occurences > 0) { number++; moddedKey = _label + getAddition(number); occurences = _db.getNumberOfKeyOccurences(moddedKey); if ((oldKey != null) && oldKey.equals(moddedKey)) occurences--; } if (!moddedKey.equals(oldKey)) { _db.setCiteKeyForEntry(_entry.getId(), moddedKey); } } return _entry; /** End of edit, Morten Alver 2004.02.04. */ } /** * 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 (modifier.equals("lower")) { label = label.toLowerCase(); } else if (modifier.equals("upper")) { label = label.toUpperCase(); } else if (modifier.equals("abbr")) { // Abbreviate - that is, // System.out.println(_sbvalue.toString()); StringBuffer abbr = new StringBuffer(); String[] words = label.toString().replaceAll("[\\{\\}']","") .split("[ \r\n\"]"); for (int word = 0; word < words.length; word++) if (words[word].length() > 0) abbr.append(words[word].charAt(0)); label = abbr.toString(); } else if (modifier.startsWith("(") && modifier.endsWith(")")) { // Alternate text modifier in parentheses. Should be inserted if // the label is empty: if (label.equals("") && (modifier.length() > 2)) return modifier.substring(1, modifier.length()-1); } else { Globals .logger("Key generator warning: unknown modifier '" + modifier + "'."); } } return label; } public static String makeLabel(BibtexEntry _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 (val.startsWith("pure")) { // remove the "pure" prefix so the remaining // code in this section functions correctly val = val.substring(4); } else { if (authString == null || authString.equals("")) { authString = _entry.getField("editor"); } } // Gather all author-related checks, so we don't // have to check all the time. if (val.equals("auth")) { return firstAuthor(authString); } else if (val.equals("authors")) { return allAuthors(authString); } else if (val.equals("authorsAlpha")) { return authorsAlpha(authString); } // Last author's last name else if (val.equals("authorLast")) { return lastAuthor(authString); } else if (val.equals("authorIni")) { String s = oneAuthorPlusIni(authString); return s == null ? "" : s; } else if (val.matches("authIni[\\d]+")) { int num = Integer.parseInt(val.substring(7)); String s = authIniN(authString, num); return s == null ? "" : s; } else if (val.equals("auth.auth.ea")) { String s = authAuthEa(authString); return s == null ? "" : s; } else if (val.equals("auth.etal")) { String s = authEtal(authString); return s == null ? "" : s; } else if (val.equals("authshort")) { String s = authshort(authString); return s == null ? "" : s; } else if (val.matches("auth[\\d]+_[\\d]+")) { String[] nums = val.substring(4).split("_"); String s = authN_M(authString, Integer.parseInt(nums[0]), Integer.parseInt(nums[1]) - 1); return s == null ? "" : s; } else if (val.matches("auth\\d+")) { // authN. First N chars of the first author's last // name. int num = Integer.parseInt(val.substring(4)); String fa = firstAuthor(authString); if (fa == null) return ""; if (num > fa.length()) num = fa.length(); return fa.substring(0, num); } else if (val.matches("authors\\d+")) { String s = 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 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 (val.equals("edtr")) { return firstAuthor(_entry.getField("editor").toString()); } else if (val.equals("editors")) { return allAuthors(_entry.getField("editor").toString()); // Last author's last name } else if (val.equals("editorLast")) { return lastAuthor(_entry.getField("editor").toString()); } else if (val.equals("editorIni")) { String s = oneAuthorPlusIni(_entry.getField("editor") .toString()); return s == null ? "" : s; } else if (val.matches("edtrIni[\\d]+")) { int num = Integer.parseInt(val.substring(7)); String s = authIniN(_entry.getField("editor").toString(), num); return s == null ? "" : s; } else if (val.matches("edtr[\\d]+_[\\d]+")) { String[] nums = val.substring(4).split("_"); String s = authN_M(_entry.getField("editor").toString(), Integer.parseInt(nums[0]), Integer.parseInt(nums[1]) - 1); return s == null ? "" : s; } else if (val.equals("edtr.edtr.ea")) { String s = authAuthEa(_entry.getField("editor").toString()); return s == null ? "" : s; } else if (val.equals("edtrshort")) { String s = authshort(_entry.getField("editor").toString()); return s == null ? "" : s; } // authN. First N chars of the first author's last // name. else if (val.matches("edtr\\d+")) { int num = Integer.parseInt(val.substring(4)); String fa = firstAuthor(_entry.getField("editor") .toString()); if (fa == null) return ""; 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 getField(_entry, val); } } else if (val.equals("firstpage")) { return firstPage(_entry.getField("pages")); } else if (val.equals("lastpage")) { return lastPage(_entry.getField("pages")); } else if (val.equals("shorttitle")) { return getTitleWords(3, _entry); } else if (val.equals("shortyear")) { String ss = _entry.getField("year"); 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.equals("veryshorttitle")) { return getTitleWords(1, _entry); } else if (val.matches("keyword\\d+")) { StringBuilder sb = new StringBuilder(); int num = Integer.parseInt(val.substring(7)); String kw = getField(_entry, "keywords"); if (kw != null) { String[] keywords = kw.split("[,;]\\s*"); if ((num > 0) && (num < keywords.length)) sb.append(keywords[num - 1].trim()); } return sb.toString(); } else { // we havent seen any special demands return getField(_entry, val); } } catch (NullPointerException ex) { return ""; } } /** * Look up a field of a BibtexEntry, 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(BibtexEntry entry, String field) { Object o = entry.getField(field); return o != null ? (String)o : ""; } /** * 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 >= CHARS.length()) { int lastChar = number % CHARS.length(); return getAddition(number/CHARS.length()-1) + CHARS.substring(lastChar, lastChar+1); } else return CHARS.substring(number, number+1); } static String getTitleWords(int number, BibtexEntry _entry) { String ss = (new RemoveLatexCommands()).format(_entry.getField("title").toString()); StringBuffer _sbvalue = new StringBuffer(), current; int piv=0, 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 StringBuffer(); // Get the next word: while ((piv<ss.length()) && !Character.isWhitespace(ss.charAt(piv)) && (ss.charAt(piv) != '-')) { current.append(ss.charAt(piv)); piv++; //System.out.println(".. "+piv+" '"+current.toString()+"'"); } piv++; // Check if it is ok: String word = current.toString().trim(); if (word.length() == 0) continue mainl; for(int _i=0; _i< Globals.SKIP_WORDS.length; _i++) { if (word.equalsIgnoreCase(Globals.SKIP_WORDS[_i])) { continue mainl; } } // If we get here, the word was accepted. if (_sbvalue.length() > 0) _sbvalue.append(" "); _sbvalue.append(word); words++; } return _sbvalue.toString(); } static String keepLettersAndDigitsOnly(String in) { StringBuilder sb = new StringBuilder(); for (int i=0; i<in.length(); i++) { if (Character.isLetterOrDigit(in.charAt(i))) sb.append(in.charAt(i)); } return sb.toString(); } /** * Tests whether a given label is unique. * @param label a <code>String</code> * @return <code>true</code> if and only if the <code>label</code> is unique */ public static boolean isLabelUnique(String label) { boolean _isUnique = true; BibtexEntry _entry; int _dbSize = _db.getEntryCount(); // run through the whole DB and check the key field // if this could be made recursive I would be very happy // it kinda sux that we have to run through the whole db. // The idea here is that if we meet NO match, the _duplicate // field will be true for (int i = 0; i < _dbSize; i++) { _entry = _db.getEntryById(String.valueOf(i)); // oh my! there is a match! we better set the uniqueness to false // and leave this for-loop all together if (_entry.getField(BibtexFields.KEY_FIELD).equals(label)) { _isUnique = false; break; } } return _isUnique; } /** * 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 */ public static String firstAuthor(String authorField) { AuthorList al = AuthorList.getAuthorList(authorField); if (al.size() == 0) return ""; String s = al.getAuthor(0).getLast(); return s != null ? s : ""; } /** * Gets the von part and the last name of the first author/editor * * @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 */ public static String firstAuthorVonAndLast(String authorField) { AuthorList al = AuthorList.getAuthorList(authorField); if (al.size() == 0) return ""; String s = al.getAuthor(0).getVon(); StringBuilder sb = new StringBuilder(); if (s != null) { sb.append(s); sb.append(' '); } s = al.getAuthor(0).getLast(); if (s != null) sb.append(s); return sb.toString(); } /** * Gets the last name of the last author/editor * @param authorField a <code>String</code> * @return the sur name of an author/editor */ private static String lastAuthor(String authorField) { String[] tokens = AuthorList.fixAuthorForAlphabetization(authorField).split("\\band\\b"); if (tokens.length > 0) { // if author is empty String[] lastAuthor = tokens[tokens.length-1].replaceAll("\\s+", " ").trim().split(" "); return lastAuthor[0]; } else return ""; } /** * Gets the last name of all authors/editors * @param authorField a <code>String</code> * @return the sur name of all authors/editors */ private static String allAuthors(String authorField) { String author = ""; // This code was part of 'ApplyRule' in 'ArticleLabelRule' String[] tokens = AuthorList.fixAuthorForAlphabetization(authorField).split("\\band\\b"); int i = 0; while (tokens.length > i) { // convert lastname, firstname to firstname lastname String[] firstAuthor = tokens[i].replaceAll("\\s+", " ").trim().split(" "); // lastname, firstname author += firstAuthor[0]; i++; } return author; } /** * 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 */ private 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 (int j=0; j<curAuthor.length; j++) { // use first character of each part of lastname authors = authors.concat(curAuthor[j].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 */ private static String NAuthors(String authorField, int n) { String author = ""; // This code was part of 'ApplyRule' in 'ArticleLabelRule' String[] tokens = AuthorList.fixAuthorForAlphabetization(authorField).split("\\band\\b"); int i = 0; while (tokens.length > i && i < n) { // convert lastname, firstname to firstname lastname String[] firstAuthor = tokens[i].replaceAll("\\s+", " ").trim().split(" "); // lastname, firstname author += firstAuthor[0]; i++; } if (tokens.length <= n) return author; return author += "EtAl"; } /** * Gets the first part of the last name of the first * author/editor, and appends the last name initial of the * remaining authors/editors. * @param authorField a <code>String</code> * @return the sur name of all authors/editors */ private static String oneAuthorPlusIni(String authorField) { final int CHARS_OF_FIRST = 5; authorField = AuthorList.fixAuthorForAlphabetization(authorField); String author = ""; // This code was part of 'ApplyRule' in 'ArticleLabelRule' String[] tokens = authorField.split("\\band\\b"); int i = 1; if (tokens.length == 0) { return author; } String[] firstAuthor = tokens[0].replaceAll("\\s+", " ").split(" "); author = firstAuthor[0].substring(0, Math.min(CHARS_OF_FIRST, firstAuthor[0].length())); while (tokens.length > i) { // convert lastname, firstname to firstname lastname author += tokens[i].trim().charAt(0); i++; } return author; } /** * 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 */ private static String authAuthEa(String authorField) { authorField = AuthorList.fixAuthorForAlphabetization(authorField); StringBuffer author = new StringBuffer(); String[] tokens = authorField.split("\\band\\b"); if (tokens.length == 0) { return ""; } author.append((tokens[0].split(","))[0]); if (tokens.length >= 2) author.append(".").append((tokens[1].split(","))[0]); if (tokens.length > 2) author.append(".ea"); return author.toString(); } /** * auth.etal format: * Isaac Newton and James Maxwell and Albert Einstein (1960) * Isaac Newton and James Maxwell (1960) * give: * Newton.etal * Newton.Maxwell */ private static String authEtal(String authorField) { authorField = AuthorList.fixAuthorForAlphabetization(authorField); StringBuffer author = new StringBuffer(); String[] tokens = authorField.split("\\band\\b"); if (tokens.length == 0) { return ""; } author.append((tokens[0].split(","))[0]); if (tokens.length == 2) author.append(".").append((tokens[1].split(","))[0]); else if (tokens.length > 2) author.append(".etal"); return author.toString(); } /** * The first N characters of the Mth author/editor. */ private static String authN_M(String authorField, int n, int m) { authorField = AuthorList.fixAuthorForAlphabetization(authorField); String[] tokens = authorField.split("\\band\\b"); if ((tokens.length <= m) || (n<0) || (m<0)) { return ""; } String lastName = (tokens[m].split(","))[0].trim(); 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 */ private static String authshort(String authorField) { authorField = AuthorList.fixAuthorForAlphabetization(authorField); StringBuffer author = new StringBuffer(); String[] tokens = authorField.split("\\band\\b"); int i = 0; if (tokens.length == 1) { author.append(authN_M(authorField,authorField.length(),0)); } else if (tokens.length >= 2) { while (tokens.length > i && i<3) { author.append(authN_M(authorField,1,i)); 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 */ public static String authIniN(String authorField, int n) { if (n <= 0) return ""; authorField = AuthorList.fixAuthorForAlphabetization(authorField); StringBuffer author = new StringBuffer(); String[] tokens = authorField.split("\\band\\b"); int i = 0; int charsAll = n / tokens.length; if (tokens.length == 0) { return author.toString(); } while (tokens.length > i) { if ( i < (n % tokens.length) ) { author.append(authN_M(authorField,charsAll+1,i)); } else { author.append(authN_M(authorField,charsAll,i)); } 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) { 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) { 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 */ public static String[] parseFieldMarker(String arg) { List<String> parts = new ArrayList<String>(); 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()]); } }