cf6c5748fc3c200033f23c44a80374e0681cfc69
[debian/jabref.git] / src / java / net / sf / jabref / export / FileActions.java
1 /*
2 Copyright (C) 2003 Nizar N. Batada, Morten O. Alver
3
4 All programs in this directory and
5 subdirectories are published under the GNU General Public License as
6 described below.
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at
11 your option) any later version.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 USA
22
23 Further information about the GNU GPL is available at:
24 http://www.gnu.org/copyleft/gpl.ja.html
25
26 */
27 package net.sf.jabref.export;
28
29 import javax.xml.transform.*;
30 import javax.xml.transform.dom.*;
31 import javax.xml.transform.stream.*;
32 import javax.swing.*;
33 import java.io.*;
34 import java.net.URL;
35 import java.util.*;
36 import java.util.regex.Pattern;
37 import java.util.regex.Matcher;
38 import java.nio.charset.UnsupportedCharsetException;
39
40 import net.sf.jabref.export.layout.Layout;
41 import net.sf.jabref.export.layout.LayoutHelper;
42 import net.sf.jabref.export.layout.LayoutFormatter;
43 import net.sf.jabref.export.layout.format.*;
44 import net.sf.jabref.*;
45 import net.sf.jabref.mods.*;
46 import ca.odell.glazedlists.BasicEventList;
47 import ca.odell.glazedlists.SortedList;
48
49 public class FileActions
50 {
51
52     private static Pattern refPat = Pattern.compile("(#[A-Za-z]+#)"); // Used to detect string references in strings
53
54
55     private static void writePreamble(Writer fw, String preamble) throws IOException {
56     if (preamble != null) {
57         fw.write("@PREAMBLE{");
58         fw.write(preamble);
59         fw.write("}"+Globals.NEWLINE +Globals.NEWLINE);
60     }
61     }
62
63     /**
64      * Write all strings in alphabetical order, modified to produce a safe (for BibTeX) order of the strings
65      * if they reference each other.
66      * @param fw The Writer to send the output to.
67      * @param database The database whose strings we should write.
68      * @throws IOException If anthing goes wrong in writing.
69      */
70     private static void writeStrings(Writer fw, BibtexDatabase database) throws IOException {
71         List strings = new ArrayList();
72         for (Iterator i = database.getStringKeySet().iterator(); i.hasNext();) {
73             strings.add(database.getString(i.next()));
74         }
75         Collections.sort(strings, new BibtexStringComparator(false));
76         // First, make a Map of all entries:
77         HashMap remaining = new HashMap();
78         for (Iterator i=strings.iterator(); i.hasNext();) {
79             BibtexString string = (BibtexString)i.next();
80             remaining.put(string.getName(), string);
81         }
82         for (Iterator i = strings.iterator(); i.hasNext();) {
83             BibtexString bs = (BibtexString) i.next();
84             if (remaining.containsKey(bs.getName()))
85                 writeString(fw, bs, remaining);
86         }
87     }
88
89     private static void writeString(Writer fw, BibtexString bs, HashMap remaining) throws IOException {
90         // First remove this from the "remaining" list so it can't cause problem with circular refs:
91         remaining.remove(bs.getName());
92
93         // Then we go through the string looking for references to other strings. If we find references
94         // to strings that we will write, but still haven't, we write those before proceeding. This ensures
95         // that the string order will be acceptable for BibTeX.
96         String content = bs.getContent();
97         Matcher m;
98         while ((m = refPat.matcher(content)).find()) {
99             String foundLabel = m.group(1);
100             int restIndex = content.indexOf(foundLabel)+foundLabel.length();
101             content = content.substring(restIndex);
102             Object referred = remaining.get(foundLabel.substring(1, foundLabel.length()-1));
103             // If the label we found exists as a key in the "remaining" Map, we go on and write it now:
104             if (referred != null)
105                 writeString(fw, (BibtexString)referred, remaining);
106         }
107
108         fw.write("@STRING{" + bs.getName() + " = ");
109         if (!bs.getContent().equals(""))
110             fw.write((new LatexFieldFormatter()).format(bs.getContent(), Globals.BIBTEX_STRING));
111         else
112             fw.write("{}");
113
114         fw.write("}" + Globals.NEWLINE + Globals.NEWLINE);
115     }
116
117     /**
118      * Writes the JabRef signature and the encoding.
119      *
120      * @param encoding String the name of the encoding, which is part of the header.
121      */
122     private static void writeBibFileHeader(Writer out, String encoding) throws IOException {
123         out.write("% ");
124       out.write(GUIGlobals.SIGNATURE);
125       out.write(" "+GUIGlobals.version+"."+Globals.NEWLINE + "% " + 
126               GUIGlobals.encPrefix+encoding+Globals.NEWLINE +Globals.NEWLINE);
127     }
128
129     /**
130      * Saves the database to file. Two boolean values indicate whether
131      * only entries with a nonzero Globals.SEARCH value and only
132      * entries with a nonzero Globals.GROUPSEARCH value should be
133      * saved. This can be used to let the user save only the results of
134      * a search. False and false means all entries are saved.
135      */
136     public static SaveSession saveDatabase(BibtexDatabase database, MetaData metaData,
137                                            File file, JabRefPreferences prefs, boolean checkSearch,
138                                            boolean checkGroup, String encoding) throws SaveException
139     {
140         BibtexEntry be = null;
141         TreeMap types = new TreeMap(); // Map to collect entry type definitions
142         // that we must save along with entries using them.
143
144         boolean backup = prefs.getBoolean("backup");
145
146         SaveSession session;
147         try {
148             session = new SaveSession(file, encoding, backup);
149         } catch (Throwable e) {
150             System.err.println("Error from encoding: '"+encoding+"' Len: "+encoding.length());
151             // we must catch all exceptions to be able notify users that
152             // saving failed, no matter what the reason was
153             // (and they won't just quit JabRef thinking
154             // everyting worked and loosing data)
155             e.printStackTrace();
156             throw new SaveException(e.getMessage());
157         }
158
159         try
160         {
161
162             // Get our data stream. This stream writes only to a temporary file, until committed.
163             VerifyingWriter fw = session.getWriter();
164
165             // Write signature.
166             writeBibFileHeader(fw, encoding);
167
168             // Write preamble if there is one.
169             writePreamble(fw, database.getPreamble());
170
171             // Write strings if there are any.
172             writeStrings(fw, database);
173
174             // Write database entries. Take care, using CrossRefEntry-
175             // Comparator, that referred entries occur after referring
176             // ones. Apart from crossref requirements, entries will be
177             // sorted as they appear on the screen.
178             List sorter = getSortedEntries(database, null, true);
179
180             FieldFormatter ff = new LatexFieldFormatter();
181
182             for (Iterator i = sorter.iterator(); i.hasNext();) {
183                 be = (BibtexEntry) (i.next());
184
185         // Check if we must write the type definition for this
186         // entry, as well. Our criterion is that all non-standard
187         // types (*not* customized standard types) must be written.
188         BibtexEntryType tp = be.getType();
189
190         if (BibtexEntryType.getStandardType(tp.getName()) == null) {
191             types.put(tp.getName(), tp);
192         }
193
194                 // Check if the entry should be written.
195                 boolean write = true;
196
197                 if (checkSearch && !nonZeroField(be, BibtexFields.SEARCH))
198                 {
199                     write = false;
200                 }
201
202                 if (checkGroup && !nonZeroField(be, BibtexFields.GROUPSEARCH))
203                 {
204                     write = false;
205                 }
206
207                 if (write)
208                 {
209                     be.write(fw, ff, true);
210                     fw.write(Globals.NEWLINE);
211                 }
212             }
213
214             // Write meta data.
215             if (metaData != null)
216             {
217                 metaData.writeMetaData(fw);
218             }
219
220         // Write type definitions, if any:
221         if (types.size() > 0) {
222         for (Iterator i=types.keySet().iterator(); i.hasNext();) {
223             CustomEntryType tp = (CustomEntryType)types.get(i.next());
224             tp.save(fw);
225             fw.write(Globals.NEWLINE);
226         }
227
228         }
229
230
231             fw.close();
232         }
233          catch (Throwable ex)
234         {
235             ex.printStackTrace();
236             try {
237                 session.cancel();
238                 //repairAfterError(file, backup, INIT_OK);
239             } catch (IOException e) {
240                 // Argh, another error? Can we do anything?
241                 e.printStackTrace();
242                 throw new SaveException(ex.getMessage()+"\n"+
243                         Globals.lang("Warning: could not complete file repair; your file may "
244                         +"have been corrupted. Error message: ")+e.getMessage());
245             }
246             throw new SaveException(ex.getMessage(), be);
247     }
248
249     return session;
250
251     }
252
253     /**
254      * Saves the database to file, including only the entries included
255      * in the supplied input array bes.
256      *
257      * @return A List containing warnings, if any.
258      */
259     public static SaveSession savePartOfDatabase(BibtexDatabase database, MetaData metaData,
260                                                  File file, JabRefPreferences prefs, BibtexEntry[] bes, String encoding) throws SaveException
261     {
262
263     TreeMap types = new TreeMap(); // Map to collect entry type definitions
264     // that we must save along with entries using them.
265
266         BibtexEntry be = null;
267         boolean backup = prefs.getBoolean("backup");
268
269         SaveSession session;
270         try {
271             session = new SaveSession(file, encoding, backup);
272         } catch (IOException e) {
273             throw new SaveException(e.getMessage());
274         }
275
276         try
277         {
278
279             // Define our data stream.
280             VerifyingWriter fw = session.getWriter();
281
282             // Write signature.
283             writeBibFileHeader(fw, encoding);
284
285             // Write preamble if there is one.
286             writePreamble(fw, database.getPreamble());
287
288             // Write strings if there are any.
289         writeStrings(fw, database);
290
291             // Write database entries. Take care, using CrossRefEntry-
292             // Comparator, that referred entries occur after referring
293             // ones. Apart from crossref requirements, entries will be
294             // sorted as they appear on the screen.
295         String pri, sec, ter;
296
297         boolean priD, secD, terD, priBinary=false;
298         if (!prefs.getBoolean("saveInStandardOrder")) {
299         // The setting is to save according to the current table order.
300             priBinary = prefs.getBoolean("priBinary");
301         pri = prefs.get("priSort");
302         sec = prefs.get("secSort");
303         // sorted as they appear on the screen.
304         ter = prefs.get("terSort");
305         priD = prefs.getBoolean("priDescending");
306         secD = prefs.getBoolean("secDescending");
307         terD = prefs.getBoolean("terDescending");
308         } else {
309         // The setting is to save in standard order: author, editor, year
310         pri = "author";
311         sec = "editor";
312         ter = "year";
313         priD = false;
314         secD = false;
315         terD = true;
316         }
317
318         List comparators = new ArrayList();
319         comparators.add(new CrossRefEntryComparator());
320         comparators.add(new FieldComparator(pri, priD));
321         comparators.add(new FieldComparator(sec, secD));
322         comparators.add(new FieldComparator(ter, terD));
323         comparators.add(new FieldComparator(BibtexFields.KEY_FIELD));
324         // Use glazed lists to get a sorted view of the entries:
325         BasicEventList entryList = new BasicEventList();
326         SortedList sorter = new SortedList(entryList, new FieldComparatorStack(comparators));
327
328         if ((bes != null) && (bes.length > 0))
329         for (int i=0; i<bes.length; i++) {
330             sorter.add(bes[i]);
331         }
332
333             FieldFormatter ff = new LatexFieldFormatter();
334
335             for (Iterator i = sorter.iterator(); i.hasNext();)
336             {
337                 be = (BibtexEntry) (i.next());
338
339         // Check if we must write the type definition for this
340         // entry, as well. Our criterion is that all non-standard
341         // types (*not* customized standard types) must be written.
342         BibtexEntryType tp = be.getType();
343         if (BibtexEntryType.getStandardType(tp.getName()) == null) {
344             types.put(tp.getName(), tp);
345         }
346
347         be.write(fw, ff, true);
348         fw.write(Globals.NEWLINE);
349         }
350
351             // Write meta data.
352             if (metaData != null)
353             {
354                 metaData.writeMetaData(fw);
355             }
356
357         // Write type definitions, if any:
358         if (types.size() > 0) {
359         for (Iterator i=types.keySet().iterator(); i.hasNext();) {
360             CustomEntryType tp = (CustomEntryType)types.get(i.next());
361             tp.save(fw);
362             fw.write(Globals.NEWLINE);
363         }
364
365         }
366
367             fw.close();
368         }
369          catch (Throwable ex)
370         {
371             try {
372                 session.cancel();
373                 //repairAfterError(file, backup, status);
374             } catch (IOException e) {
375                 // Argh, another error? Can we do anything?
376                 e.printStackTrace();
377                 throw new SaveException(ex.getMessage()+"\n"+
378                         Globals.lang("Warning: could not complete file repair; your file may "
379                         +"have been corrupted. Error message: ")+e.getMessage());
380             }
381             throw new SaveException(ex.getMessage(), be);
382     }
383
384         return session;
385
386     }
387
388     public static void exportToCSV(BibtexDatabase database,
389                                    File outFile, JabRefPreferences prefs)
390         throws Exception {
391
392     HashMap fieldFormatters = new HashMap();
393     fieldFormatters.put("default", new RemoveLatexCommands());
394     fieldFormatters.put("author", new Object[] {new AuthorLastFirst(),
395                             new RemoveLatexCommands()});
396     fieldFormatters.put("pdf", new ResolvePDF());
397
398     String SEPARATOR = "\t";
399     List sorted = getSortedEntries(database, null, true);
400     Set fields = new TreeSet();
401     for (int i=0, len=BibtexFields.numberOfPublicFields(); i<len; i++)
402         fields.add(BibtexFields.getFieldName(i));
403
404     //  try {
405     Object[] o = fields.toArray();
406     FileWriter out = new FileWriter(outFile);
407     out.write((String)o[0]);
408     for (int i=1; i<o.length; i++) {
409         out.write(SEPARATOR+(String)o[i]);
410     }
411     out.write(Globals.NEWLINE);
412
413     for (Iterator i=sorted.iterator(); i.hasNext();) {
414         BibtexEntry entry = (BibtexEntry)i.next();
415         writeField(database, entry, (String)o[0], fieldFormatters, out);
416         for (int j=1; j<o.length; j++) {
417         out.write(SEPARATOR);
418         writeField(database, entry, (String)o[j], fieldFormatters, out);
419         }
420         out.write(Globals.NEWLINE);
421     }
422
423
424     out.close();
425     //  } catch (Throwable ex) {}
426
427
428     }
429
430
431
432     private static void writeField(BibtexDatabase database, BibtexEntry entry, String field,
433                                    HashMap fieldFormatters, Writer out)
434     throws IOException {
435
436     String s = BibtexDatabase.getResolvedField(field, entry, database);
437         
438     Object form = fieldFormatters.get(field);
439     if (form == null)
440         form = fieldFormatters.get("default");
441
442     if (form instanceof LayoutFormatter) {
443         s = ((LayoutFormatter)form).format(s);
444     } else if (form instanceof Object[]) {
445         Object[] forms = (Object[])form;
446         for (int i=0; i<forms.length; i++) {
447         s = ((LayoutFormatter)(forms[i])).format(s);
448         }
449     }
450
451     out.write(s);
452     }
453
454     /**
455      * This method attempts to get a Reader for the file path given, either by
456      * loading it as a resource (from within jar), or as a normal file.
457      * If unsuccessful (e.g. file not found), an IOException is thrown.
458      */
459     public static Reader getReader(String name) throws IOException {
460       Reader reader = null;
461       // Try loading as a resource first. This works for files inside the jar:
462       URL reso = Globals.class.getResource(name);
463
464       // If that didn't work, try loading as a normal file URL:
465       if (reso != null) {
466         try {
467           reader = new InputStreamReader(reso.openStream());
468         } catch (FileNotFoundException ex) {
469           throw new IOException(Globals.lang("Could not find layout file")+": '"+name+"'.");
470         }
471       } else {
472         File f = new File(name);
473         try {
474           reader = new FileReader(f);
475         } catch (FileNotFoundException ex) {
476           throw new IOException(Globals.lang("Could not find layout file")+": '"+name+"'.");
477         }
478       }
479
480       return reader;
481     }
482
483     /*
484     * We have begun to use getSortedEntries() for both database save operations
485     * and non-database save operations.  In a non-database save operation
486     * (such as the exportDatabase call), we do not wish to use the
487     * global preference of saving in standard order.
488     */
489     public static List getSortedEntries(BibtexDatabase database, Set keySet, boolean isSaveOperation) {
490         FieldComparatorStack comparatorStack = null;
491
492         if (Globals.prefs.getBoolean("saveInOriginalOrder")) {
493             // Sort entries based on their creation order, utilizing the fact
494             // that IDs used for entries are increasing, sortable numbers.
495             List comparators = new ArrayList();
496             comparators.add(new CrossRefEntryComparator());
497             comparators.add(new IdComparator());
498             comparatorStack = new FieldComparatorStack(comparators);
499
500         } else {
501             String pri, sec, ter;
502             boolean priD, secD, terD, priBinary = false;
503
504
505             if (!isSaveOperation || !Globals.prefs.getBoolean("saveInStandardOrder")) {
506                 // The setting is to save according to the current table order.
507                 priBinary = Globals.prefs.getBoolean("priBinary");
508                 pri = Globals.prefs.get("priSort");
509                 sec = Globals.prefs.get("secSort");
510                 // sorted as they appear on the screen.
511                 ter = Globals.prefs.get("terSort");
512                 priD = Globals.prefs.getBoolean("priDescending");
513                 secD = Globals.prefs.getBoolean("secDescending");
514                 terD = Globals.prefs.getBoolean("terDescending");
515             } else {
516                 // The setting is to save in standard order: author, editor, year
517                 pri = "author";
518                 sec = "editor";
519                 ter = "year";
520                 priD = false;
521                 secD = false;
522                 terD = true;
523             }
524
525             List comparators = new ArrayList();
526             if (isSaveOperation)
527                 comparators.add(new CrossRefEntryComparator());
528             comparators.add(new FieldComparator(pri, priD));
529             comparators.add(new FieldComparator(sec, secD));
530             comparators.add(new FieldComparator(ter, terD));
531             comparators.add(new FieldComparator(BibtexFields.KEY_FIELD));
532
533             comparatorStack = new FieldComparatorStack(comparators);
534         }
535         // Use glazed lists to get a sorted view of the entries:
536         BasicEventList entryList = new BasicEventList();
537         SortedList sorter = new SortedList(entryList, comparatorStack);
538
539         if (keySet == null)
540             keySet = database.getKeySet();
541
542         if (keySet != null) {
543             Iterator i = keySet.iterator();
544
545             for (; i.hasNext();) {
546                 sorter.add(database.getEntryById((String) (i.next())));
547             }
548         }
549         return sorter;
550     }
551
552     /** Returns true iff the entry has a nonzero value in its field.
553      */
554     private static boolean nonZeroField(BibtexEntry be, String field)
555     {
556         String o = (String) (be.getField(field));
557
558         return ((o != null) && !o.equals("0"));
559     }
560 }
561
562
563
564 ///////////////////////////////////////////////////////////////////////////////
565 //  END OF FILE.
566 ///////////////////////////////////////////////////////////////////////////////