/* Copyright (C) 2003-2011 JabRef contributors. 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.export; import java.util.Vector; import net.sf.jabref.BibtexFields; import net.sf.jabref.GUIGlobals; import net.sf.jabref.Globals; import net.sf.jabref.Util; public class LatexFieldFormatter implements FieldFormatter { StringBuffer sb; int col; // First line usually starts about so much further to the right. final int STARTCOL = 4; private boolean neverFailOnHashes = false; public void setNeverFailOnHashes(boolean neverFailOnHashes) { this.neverFailOnHashes = neverFailOnHashes; } public String format(String text, String fieldName) throws IllegalArgumentException { if (Globals.prefs.putBracesAroundCapitals(fieldName) && !Globals.BIBTEX_STRING.equals(fieldName)) { text = Util.putBracesAroundCapitals(text); } // If the field is non-standard, we will just append braces, // wrap and write. boolean resolveStrings = true; if (Globals.prefs.getBoolean("resolveStringsAllFields")) { // Resolve strings for all fields except some: String[] exceptions = Globals.prefs.getStringArray("doNotResolveStringsFor"); for (int i = 0; i < exceptions.length; i++) { if (exceptions[i].equals(fieldName)) { resolveStrings = false; break; } } } else { // Default operation - we only resolve strings for standard fields: resolveStrings = BibtexFields.isStandardField(fieldName) || Globals.BIBTEX_STRING.equals(fieldName); } if (!resolveStrings) { int brc = 0; boolean ok = true; for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); //Util.pr(""+c); if (c == '{') brc++; if (c == '}') brc--; if (brc < 0) { ok = false; break; } } if (brc > 0) ok = false; if (!ok) throw new IllegalArgumentException("Curly braces { and } must be balanced."); sb = new StringBuffer("{"); // No formatting at all for these fields, to allow custom formatting? //if (Globals.prefs.getBoolean("preserveFieldFormatting")) // sb.append(text); //else if (!Globals.prefs.isNonWrappableField(fieldName)) sb.append(Util.wrap2(text, GUIGlobals.LINE_LENGTH)); else sb.append(text); sb.append('}'); return sb.toString(); } sb = new StringBuffer(); int pivot = 0, pos1, pos2; col = STARTCOL; // Here we assume that the user encloses any bibtex strings in #, e.g.: // #jan# - #feb# // ...which will be written to the file like this: // jan # { - } # feb checkBraces(text); while (pivot < text.length()) { int goFrom = pivot; pos1 = pivot; while (goFrom == pos1) { pos1 = text.indexOf('#', goFrom); if ((pos1 > 0) && (text.charAt(pos1 - 1) == '\\')) { goFrom = pos1 + 1; pos1++; } else goFrom = pos1 - 1; // Ends the loop. } if (pos1 == -1) { pos1 = text.length(); // No more occurences found. pos2 = -1; } else { pos2 = text.indexOf('#', pos1 + 1); if (pos2 == -1) { if (!neverFailOnHashes) { throw new IllegalArgumentException (Globals.lang("The # character is not allowed in BibTeX strings unless escaped as in '\\#'.") + "\n" + Globals.lang("In JabRef, use pairs of # characters to indicate a string.") + "\n" + Globals.lang("Note that the entry causing the problem has been selected.")); } else { pos1 = text.length(); // just write out the rest of the text, and throw no exception } } } if (pos1 > pivot) writeText(text, pivot, pos1); if ((pos1 < text.length()) && (pos2 - 1 > pos1)) // We check that the string label is not empty. That means // an occurence of ## will simply be ignored. Should it instead // cause an error message? writeStringLabel(text, pos1 + 1, pos2, (pos1 == pivot), (pos2 + 1 == text.length())); if (pos2 > -1) pivot = pos2 + 1; else pivot = pos1 + 1; //if (tell++ > 10) System.exit(0); } if (!Globals.prefs.isNonWrappableField(fieldName)) return Util.wrap2(sb.toString(), GUIGlobals.LINE_LENGTH); else return sb.toString(); } private void writeText(String text, int start_pos, int end_pos) { /*sb.append("{"); sb.append(text.substring(start_pos, end_pos)); sb.append("}");*/ sb.append('{'); boolean escape = false, inCommandName = false, inCommand = false, inCommandOption = false; int nestedEnvironments = 0; StringBuffer commandName = new StringBuffer(); char c; for (int i = start_pos; i < end_pos; i++) { c = text.charAt(i); // Track whether we are in a LaTeX command of some sort. if (Character.isLetter(c) && (escape || inCommandName)) { inCommandName = true; if (!inCommandOption) commandName.append(c); } else if (Character.isWhitespace(c) && (inCommand || inCommandOption)) { //System.out.println("whitespace here"); } else if (inCommandName) { // This means the command name is ended. // Perhaps the beginning of an argument: if (c == '[') { inCommandOption = true; } // Or the end of an argument: else if (inCommandOption && (c == ']')) inCommandOption = false; // Or the beginning of the command body: else if (!inCommandOption && (c == '{')) { //System.out.println("Read command: '"+commandName.toString()+"'"); inCommandName = false; inCommand = true; } // Or simply the end of this command altogether: else { //System.out.println("I think I read command: '"+commandName.toString()+"'"); commandName.delete(0, commandName.length()); inCommandName = false; } } // If we are in a command body, see if it has ended: if (inCommand && (c == '}')) { //System.out.println("nestedEnvironments = " + nestedEnvironments); //System.out.println("Done with command: '"+commandName.toString()+"'"); if (commandName.toString().equals("begin")) { nestedEnvironments++; } if (nestedEnvironments > 0 && commandName.toString().equals("end")) { nestedEnvironments--; } //System.out.println("nestedEnvironments = " + nestedEnvironments); commandName.delete(0, commandName.length()); inCommand = false; } // We add a backslash before any ampersand characters, with one exception: if // we are inside an \\url{...} command, we should write it as it is. Maybe. if ((c == '&') && !escape && !(inCommand && commandName.toString().equals("url")) && (nestedEnvironments == 0)) { sb.append("\\&"); } else sb.append(c); escape = (c == '\\'); } sb.append('}'); } private void writeStringLabel(String text, int start_pos, int end_pos, boolean first, boolean last) { //sb.append(Util.wrap2((first ? "" : " # ") + text.substring(start_pos, end_pos) // + (last ? "" : " # "), GUIGlobals.LINE_LENGTH)); putIn((first ? "" : " # ") + text.substring(start_pos, end_pos) + (last ? "" : " # ")); } private void putIn(String s) { sb.append(Util.wrap2(s, GUIGlobals.LINE_LENGTH)); } private void checkBraces(String text) throws IllegalArgumentException { Vector<Integer> left = new Vector<Integer>(5, 3), right = new Vector<Integer>(5, 3); int current = -1; // First we collect all occurences: while ((current = text.indexOf('{', current + 1)) != -1) left.add(new Integer(current)); while ((current = text.indexOf('}', current + 1)) != -1) right.add(new Integer(current)); // Then we throw an exception if the error criteria are met. if ((right.size() > 0) && (left.size() == 0)) throw new IllegalArgumentException ("'}' character ends string prematurely."); if ((right.size() > 0) && ((right.elementAt(0)).intValue() < (left.elementAt(0)).intValue())) throw new IllegalArgumentException ("'}' character ends string prematurely."); if (left.size() != right.size()) throw new IllegalArgumentException ("Braces don't match."); } }