5ce5222f67fd883b1f34c1475c40607326800097
[debian/jabref.git] / src / java / net / sf / jabref / export / LatexFieldFormatter.java
1 /*  Copyright (C) 2003-2011 JabRef contributors.
2     This program is free software; you can redistribute it and/or modify
3     it under the terms of the GNU General Public License as published by
4     the Free Software Foundation; either version 2 of the License, or
5     (at your option) any later version.
6
7     This program is distributed in the hope that it will be useful,
8     but WITHOUT ANY WARRANTY; without even the implied warranty of
9     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10     GNU General Public License for more details.
11
12     You should have received a copy of the GNU General Public License along
13     with this program; if not, write to the Free Software Foundation, Inc.,
14     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
15 */
16 package net.sf.jabref.export;
17
18 import java.util.Vector;
19
20 import net.sf.jabref.BibtexFields;
21 import net.sf.jabref.GUIGlobals;
22 import net.sf.jabref.Globals;
23 import net.sf.jabref.Util;
24
25 public class LatexFieldFormatter implements FieldFormatter {
26
27     StringBuffer sb;
28     int col; // First line usually starts about so much further to the right.
29     final int STARTCOL = 4;
30     private boolean neverFailOnHashes = false;
31
32     public void setNeverFailOnHashes(boolean neverFailOnHashes) {
33         this.neverFailOnHashes = neverFailOnHashes;
34     }
35
36     public String format(String text, String fieldName)
37             throws IllegalArgumentException {
38         if (text == null) return Globals.prefs.getValueDelimiters(0)
39                 + "" + Globals.prefs.getValueDelimiters(1);
40
41         if (Globals.prefs.putBracesAroundCapitals(fieldName) && !Globals.BIBTEX_STRING.equals(fieldName)) {
42             text = Util.putBracesAroundCapitals(text);
43         }
44
45         // If the field is non-standard, we will just append braces,
46         // wrap and write.
47         boolean resolveStrings = true;
48         if (Globals.prefs.getBoolean("resolveStringsAllFields")) {
49             // Resolve strings for all fields except some:
50             String[] exceptions = Globals.prefs.getStringArray("doNotResolveStringsFor");
51             for (int i = 0; i < exceptions.length; i++) {
52                 if (exceptions[i].equals(fieldName)) {
53                     resolveStrings = false;
54                     break;
55                 }
56             }
57         } else {
58             // Default operation - we only resolve strings for standard fields:
59             resolveStrings = BibtexFields.isStandardField(fieldName)
60                     || Globals.BIBTEX_STRING.equals(fieldName);
61         }
62         if (!resolveStrings) {
63             int brc = 0;
64             boolean ok = true;
65             for (int i = 0; i < text.length(); i++) {
66                 char c = text.charAt(i);
67                 //Util.pr(""+c);
68                 if (c == '{') brc++;
69                 if (c == '}') brc--;
70                 if (brc < 0) {
71                     ok = false;
72                     break;
73                 }
74             }
75             if (brc > 0)
76                 ok = false;
77             if (!ok)
78                 throw new IllegalArgumentException("Curly braces { and } must be balanced.");
79
80             sb = new StringBuffer(
81                     Globals.prefs.getValueDelimiters(0) + "");
82             // No formatting at all for these fields, to allow custom formatting?
83             //if (Globals.prefs.getBoolean("preserveFieldFormatting"))
84             //  sb.append(text);
85             //else
86             //if (!Globals.prefs.isNonWrappableField(fieldName))
87             //    sb.append(Util.wrap2(text, GUIGlobals.LINE_LENGTH));
88             //else
89                 sb.append(text);
90
91             sb.append(Globals.prefs.getValueDelimiters(1));
92
93             return sb.toString();
94         }
95
96         sb = new StringBuffer();
97         int pivot = 0, pos1, pos2;
98         col = STARTCOL;
99         // Here we assume that the user encloses any bibtex strings in #, e.g.:
100         // #jan# - #feb#
101         // ...which will be written to the file like this:
102         // jan # { - } # feb
103         checkBraces(text);
104
105
106         while (pivot < text.length()) {
107             int goFrom = pivot;
108             pos1 = pivot;
109             while (goFrom == pos1) {
110                 pos1 = text.indexOf('#', goFrom);
111                 if ((pos1 > 0) && (text.charAt(pos1 - 1) == '\\')) {
112                     goFrom = pos1 + 1;
113                     pos1++;
114                 } else
115                     goFrom = pos1 - 1; // Ends the loop.
116             }
117
118             if (pos1 == -1) {
119                 pos1 = text.length(); // No more occurences found.
120                 pos2 = -1;
121             } else {
122                 pos2 = text.indexOf('#', pos1 + 1);
123                 if (pos2 == -1) {
124                     if (!neverFailOnHashes) {
125                         throw new IllegalArgumentException
126                                 (Globals.lang("The # character is not allowed in BibTeX strings unless escaped as in '\\#'.") + "\n" +
127                                         Globals.lang("In JabRef, use pairs of # characters to indicate a string.") + "\n" +
128                                         Globals.lang("Note that the entry causing the problem has been selected."));
129                     } else {
130                         pos1 = text.length(); // just write out the rest of the text, and throw no exception
131                     }
132                 }
133             }
134
135             if (pos1 > pivot)
136                 writeText(text, pivot, pos1);
137             if ((pos1 < text.length()) && (pos2 - 1 > pos1))
138                 // We check that the string label is not empty. That means
139                 // an occurence of ## will simply be ignored. Should it instead
140                 // cause an error message?
141                 writeStringLabel(text, pos1 + 1, pos2, (pos1 == pivot),
142                         (pos2 + 1 == text.length()));
143
144             if (pos2 > -1) pivot = pos2 + 1;
145             else pivot = pos1 + 1;
146             //if (tell++ > 10) System.exit(0);
147         }
148
149         //if (!Globals.prefs.isNonWrappableField(fieldName))
150         //    return Util.wrap2(sb.toString(), GUIGlobals.LINE_LENGTH);
151         //else
152             return sb.toString();
153
154
155     }
156
157     private void writeText(String text, int start_pos,
158                            int end_pos) {
159         /*sb.append("{");
160         sb.append(text.substring(start_pos, end_pos));
161         sb.append("}");*/
162         sb.append(Globals.prefs.getValueDelimiters(0));
163         boolean escape = false, inCommandName = false, inCommand = false,
164                 inCommandOption = false;
165         int nestedEnvironments = 0;
166         StringBuffer commandName = new StringBuffer();
167         char c;
168         for (int i = start_pos; i < end_pos; i++) {
169             c = text.charAt(i);
170
171             // Track whether we are in a LaTeX command of some sort.
172             if (Character.isLetter(c) && (escape || inCommandName)) {
173                 inCommandName = true;
174                 if (!inCommandOption)
175                     commandName.append(c);
176             } else if (Character.isWhitespace(c) && (inCommand || inCommandOption)) {
177                 //System.out.println("whitespace here");
178             } else if (inCommandName) {
179                 // This means the command name is ended.
180                 // Perhaps the beginning of an argument:
181                 if (c == '[') {
182                     inCommandOption = true;
183                 }
184                 // Or the end of an argument:
185                 else if (inCommandOption && (c == ']'))
186                     inCommandOption = false;
187                     // Or the beginning of the command body:
188                 else if (!inCommandOption && (c == '{')) {
189                     //System.out.println("Read command: '"+commandName.toString()+"'");
190                     inCommandName = false;
191                     inCommand = true;
192                 }
193                 // Or simply the end of this command altogether:
194                 else {
195                     //System.out.println("I think I read command: '"+commandName.toString()+"'");
196
197                     commandName.delete(0, commandName.length());
198                     inCommandName = false;
199                 }
200             }
201             // If we are in a command body, see if it has ended:
202             if (inCommand && (c == '}')) {
203                 //System.out.println("nestedEnvironments = " + nestedEnvironments);
204                 //System.out.println("Done with command: '"+commandName.toString()+"'");
205                 if (commandName.toString().equals("begin")) {
206                     nestedEnvironments++;
207                 }
208                 if (nestedEnvironments > 0 && commandName.toString().equals("end")) {
209                     nestedEnvironments--;
210                 }
211                 //System.out.println("nestedEnvironments = " + nestedEnvironments);
212
213                 commandName.delete(0, commandName.length());
214                 inCommand = false;
215             }
216
217             // We add a backslash before any ampersand characters, with one exception: if
218             // we are inside an \\url{...} command, we should write it as it is. Maybe.
219             if ((c == '&') && !escape &&
220                     !(inCommand && commandName.toString().equals("url")) &&
221                     (nestedEnvironments == 0)) {
222                 sb.append("\\&");
223             } else
224                 sb.append(c);
225             escape = (c == '\\');
226         }
227         sb.append(Globals.prefs.getValueDelimiters(1));
228     }
229
230     private void writeStringLabel(String text, int start_pos, int end_pos,
231                                   boolean first, boolean last) {
232         //sb.append(Util.wrap2((first ? "" : " # ") + text.substring(start_pos, end_pos)
233         //                   + (last ? "" : " # "), GUIGlobals.LINE_LENGTH));
234         putIn((first ? "" : " # ") + text.substring(start_pos, end_pos)
235                 + (last ? "" : " # "));
236     }
237
238     private void putIn(String s) {
239         sb.append(Util.wrap2(s, GUIGlobals.LINE_LENGTH));
240     }
241
242
243     private void checkBraces(String text) throws IllegalArgumentException {
244
245         Vector<Integer>
246                 left = new Vector<Integer>(5, 3),
247                 right = new Vector<Integer>(5, 3);
248         int current = -1;
249
250         // First we collect all occurences:
251         while ((current = text.indexOf('{', current + 1)) != -1)
252             left.add(new Integer(current));
253         while ((current = text.indexOf('}', current + 1)) != -1)
254             right.add(new Integer(current));
255
256         // Then we throw an exception if the error criteria are met.
257         if ((right.size() > 0) && (left.size() == 0))
258             throw new IllegalArgumentException
259                     ("'}' character ends string prematurely.");
260         if ((right.size() > 0) && ((right.elementAt(0)).intValue()
261                 < (left.elementAt(0)).intValue()))
262             throw new IllegalArgumentException
263                     ("'}' character ends string prematurely.");
264         if (left.size() != right.size())
265             throw new IllegalArgumentException
266                     ("Braces don't match.");
267
268     }
269
270 }