]> git.lyx.org Git - lyx.git/blob - src/frontends/controllers/frontend_helpers.cpp
* src/frontends/controllers/frontend_helpers.cpp:
[lyx.git] / src / frontends / controllers / frontend_helpers.cpp
1 /**
2  * \file frontend_helpers.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  * \author Herbert Voß
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "frontend_helpers.h"
15
16 #include "Buffer.h"
17 #include "BufferParams.h"
18 #include "Color.h"
19 #include "debug.h"
20 #include "gettext.h"
21 #include "Language.h"
22 #include "Length.h"
23
24 #include "frontends/FileDialog.h"
25 #include "frontends/alert.h"
26
27 #include "support/filetools.h"
28 #include "support/lstrings.h"
29 #include "support/Package.h"
30 #include "support/filetools.h"
31 #include "support/lstrings.h"
32 #include "support/lyxalgo.h"
33 #include "support/os.h"
34 #include "support/Package.h"
35 #include "support/Path.h"
36 #include "support/Systemcall.h"
37
38 #include <boost/cregex.hpp>
39 #include <boost/regex.hpp>
40
41 #include <algorithm>
42 #include <fstream>
43 #include <locale>
44
45 using std::string;
46 using std::vector;
47 using std::pair;
48 using std::endl;
49
50 namespace lyx {
51
52 using support::ascii_lowercase;
53 using support::bformat;
54 using support::compare_ascii_no_case;
55 using support::contains;
56 using support::getVectorFromString;
57 using support::ltrim;
58 using support::prefixIs;
59 using support::rtrim;
60 using support::split;
61 using support::subst;
62 using support::token;
63 using support::trim;
64
65 namespace biblio {
66
67 namespace {
68
69 vector<string> const init_possible_cite_commands()
70 {
71         char const * const pos[] = {
72                 "cite",
73                 "citet", "citep", "citealt", "citealp",
74                 "citeauthor", "citeyear", "citeyearpar",
75                 "citet*", "citep*", "citealt*", "citealp*", "citeauthor*",
76                 "Citet",  "Citep",  "Citealt",  "Citealp",  "Citeauthor",
77                 "Citet*", "Citep*", "Citealt*", "Citealp*", "Citeauthor*",
78                 "fullcite",
79                 "footcite", "footcitet", "footcitep", "footcitealt",
80                 "footcitealp", "footciteauthor", "footciteyear",
81                 "footciteyearpar",
82                 "citefield",
83                 "citetitle",
84                 "cite*"
85         };
86         size_t const size_pos = sizeof(pos) / sizeof(pos[0]);
87
88         return vector<string>(pos, pos + size_pos);
89 }
90
91
92 vector<string> const & possible_cite_commands()
93 {
94         static vector<string> const pos = init_possible_cite_commands();
95         return pos;
96 }
97
98
99 bool is_possible_cite_command(string const & input)
100 {
101         vector<string> const & possibles = possible_cite_commands();
102         vector<string>::const_iterator const end = possibles.end();
103         return std::find(possibles.begin(), end, input) != end;
104 }
105
106
107 string const default_cite_command(CiteEngine engine)
108 {
109         string str;
110         switch (engine) {
111         case ENGINE_BASIC:
112                 str = "cite";
113                 break;
114         case ENGINE_NATBIB_AUTHORYEAR:
115                 str = "citet";
116                 break;
117         case ENGINE_NATBIB_NUMERICAL:
118                 str = "citep";
119                 break;
120         case ENGINE_JURABIB:
121                 str = "cite";
122                 break;
123         }
124         return str;
125 }
126
127
128 static const docstring TheBibliographyRef(from_ascii("TheBibliographyRef"));
129
130 } // namespace anon
131
132
133 string const asValidLatexCommand(string const & input,
134                                  CiteEngine const engine)
135 {
136         string const default_str = default_cite_command(engine);
137         if (!is_possible_cite_command(input))
138                 return default_str;
139
140         string output;
141         switch (engine) {
142         case ENGINE_BASIC:
143                 output = default_str;
144                 break;
145
146         case ENGINE_NATBIB_AUTHORYEAR:
147         case ENGINE_NATBIB_NUMERICAL:
148                 if (input == "cite" || input == "citefield" ||
149                     input == "citetitle" || input == "cite*")
150                         output = default_str;
151                 else if (prefixIs(input, "foot"))
152                         output = input.substr(4);
153                 else
154                         output = input;
155                 break;
156
157         case ENGINE_JURABIB: {
158                 // Jurabib does not support the 'uppercase' natbib style.
159                 if (input[0] == 'C')
160                         output = string(1, 'c') + input.substr(1);
161                 else
162                         output = input;
163
164                 // Jurabib does not support the 'full' natbib style.
165                 string::size_type const n = output.size() - 1;
166                 if (output != "cite*" && output[n] == '*')
167                         output = output.substr(0, n);
168
169                 break;
170         }
171         }
172
173         return output;
174 }
175
176
177 docstring const familyName(docstring const & name)
178 {
179         if (name.empty())
180                 return docstring();
181
182         // Very simple parser
183         docstring fname = name;
184
185         // possible authorname combinations are:
186         // "Surname, FirstName"
187         // "Surname, F."
188         // "FirstName Surname"
189         // "F. Surname"
190         docstring::size_type idx = fname.find(',');
191         if (idx != docstring::npos)
192                 return ltrim(fname.substr(0, idx));
193         idx = fname.rfind('.');
194         if (idx != docstring::npos && idx + 1 < fname.size())
195                 fname = ltrim(fname.substr(idx + 1));
196         // test if we have a LaTeX Space in front
197         if (fname[0] == '\\')
198                 return fname.substr(2);
199
200         return rtrim(fname);
201 }
202
203
204 docstring const getAbbreviatedAuthor(InfoMap const & map, string const & key)
205 {
206         BOOST_ASSERT(!map.empty());
207
208         InfoMap::const_iterator it = map.find(key);
209         if (it == map.end())
210                 return docstring();
211         docstring const & data = it->second;
212
213         // Is the entry a BibTeX one or one from lyx-layout "bibliography"?
214         docstring::size_type const pos = data.find(TheBibliographyRef);
215         if (pos != docstring::npos) {
216                 if (pos <= 2) {
217                         return docstring();
218                 }
219
220                 docstring const opt = trim(data.substr(0, pos - 1));
221                 if (opt.empty())
222                         return docstring();
223
224                 docstring authors;
225                 split(opt, authors, '(');
226                 return authors;
227         }
228
229         docstring author = parseBibTeX(data, "author");
230
231         if (author.empty())
232                 author = parseBibTeX(data, "editor");
233
234         if (author.empty()) {
235                 author = parseBibTeX(data, "key");
236                 if (author.empty())
237                         // FIXME UNICODE
238                         return from_utf8(key);
239                 return author;
240         }
241
242         vector<docstring> const authors = getVectorFromString(author, from_ascii(" and "));
243         if (authors.empty())
244                 return author;
245
246         if (authors.size() == 2)
247                 return bformat(_("%1$s and %2$s"),
248                         familyName(authors[0]), familyName(authors[1]));
249
250         if (authors.size() > 2)
251                 return bformat(_("%1$s et al."), familyName(authors[0]));
252
253         return familyName(authors[0]);
254 }
255
256
257 docstring const getYear(InfoMap const & map, string const & key)
258 {
259         BOOST_ASSERT(!map.empty());
260
261         InfoMap::const_iterator it = map.find(key);
262         if (it == map.end())
263                 return docstring();
264         docstring const & data = it->second;
265
266         // Is the entry a BibTeX one or one from lyx-layout "bibliography"?
267         docstring::size_type const pos = data.find(TheBibliographyRef);
268         if (pos != docstring::npos) {
269                 if (pos <= 2) {
270                         return docstring();
271                 }
272
273                 docstring const opt =
274                         trim(data.substr(0, pos - 1));
275                 if (opt.empty())
276                         return docstring();
277
278                 docstring authors;
279                 docstring const tmp = split(opt, authors, '(');
280                 docstring year;
281                 split(tmp, year, ')');
282                 return year;
283
284         }
285
286         docstring year = parseBibTeX(data, "year");
287         if (year.empty())
288                 year = _("No year");
289
290         return year;
291 }
292
293
294 namespace {
295
296 // A functor for use with std::sort, leading to case insensitive sorting
297 class compareNoCase: public std::binary_function<string, string, bool>
298 {
299 public:
300         bool operator()(string const & s1, string const & s2) const {
301                 return compare_ascii_no_case(s1, s2) < 0;
302         }
303 };
304
305 } // namespace anon
306
307
308 vector<string> const getKeys(InfoMap const & map)
309 {
310         vector<string> bibkeys;
311         InfoMap::const_iterator it  = map.begin();
312         InfoMap::const_iterator end = map.end();
313         for (; it != end; ++it) {
314                 bibkeys.push_back(it->first);
315         }
316
317         std::sort(bibkeys.begin(), bibkeys.end(), compareNoCase());
318         return bibkeys;
319 }
320
321
322 docstring const getInfo(InfoMap const & map, string const & key)
323 {
324         BOOST_ASSERT(!map.empty());
325
326         InfoMap::const_iterator it = map.find(key);
327         if (it == map.end())
328                 return docstring();
329         docstring const & data = it->second;
330
331         // is the entry a BibTeX one or one from lyx-layout "bibliography"?
332         docstring::size_type const pos = data.find(TheBibliographyRef);
333         if (pos != docstring::npos) {
334                 docstring::size_type const pos2 = pos + TheBibliographyRef.size();
335                 docstring const info = trim(data.substr(pos2));
336                 return info;
337         }
338
339         // Search for all possible "required" keys
340         docstring author = parseBibTeX(data, "author");
341         if (author.empty())
342                 author = parseBibTeX(data, "editor");
343
344         docstring year      = parseBibTeX(data, "year");
345         docstring title     = parseBibTeX(data, "title");
346         docstring booktitle = parseBibTeX(data, "booktitle");
347         docstring chapter   = parseBibTeX(data, "chapter");
348         docstring number    = parseBibTeX(data, "number");
349         docstring volume    = parseBibTeX(data, "volume");
350         docstring pages     = parseBibTeX(data, "pages");
351         docstring annote    = parseBibTeX(data, "annote");
352         docstring media     = parseBibTeX(data, "journal");
353         if (media.empty())
354                 media = parseBibTeX(data, "publisher");
355         if (media.empty())
356                 media = parseBibTeX(data, "school");
357         if (media.empty())
358                 media = parseBibTeX(data, "institution");
359
360         odocstringstream result;
361         if (!author.empty())
362                 result << author << ", ";
363         if (!title.empty())
364                 result << title;
365         if (!booktitle.empty())
366                 result << ", in " << booktitle;
367         if (!chapter.empty())
368                 result << ", Ch. " << chapter;
369         if (!media.empty())
370                 result << ", " << media;
371         if (!volume.empty())
372                 result << ", vol. " << volume;
373         if (!number.empty())
374                 result << ", no. " << number;
375         if (!pages.empty())
376                 result << ", pp. " << pages;
377         if (!year.empty())
378                 result << ", " << year;
379         if (!annote.empty())
380                 result << "\n\n" << annote;
381
382         docstring const result_str = rtrim(result.str());
383         if (!result_str.empty())
384                 return result_str;
385
386         // This should never happen (or at least be very unusual!)
387         return data;
388 }
389
390
391 namespace {
392
393 // Escape special chars.
394 // All characters are literals except: '.|*?+(){}[]^$\'
395 // These characters are literals when preceded by a "\", which is done here
396 // @todo: This function should be moved to support, and then the test in tests
397 //        should be moved there as well.
398 string const escape_special_chars(string const & expr)
399 {
400         // Search for all chars '.|*?+(){}[^$]\'
401         // Note that '[' and '\' must be escaped.
402         // This is a limitation of boost::regex, but all other chars in BREs
403         // are assumed literal.
404         boost::regex reg("[].|*?+(){}^$\\[\\\\]");
405
406         // $& is a perl-like expression that expands to all
407         // of the current match
408         // The '$' must be prefixed with the escape character '\' for
409         // boost to treat it as a literal.
410         // Thus, to prefix a matched expression with '\', we use:
411         return boost::regex_replace(expr, reg, "\\\\$&");
412 }
413
414
415 // A functor for use with std::find_if, used to ascertain whether a
416 // data entry matches the required regex_
417 // @throws: boost::regex_error if the supplied regex pattern is not valid
418 // @todo: This function should be moved to support.
419 class RegexMatch : public std::unary_function<string, bool>
420 {
421 public:
422         // re and icase are used to construct an instance of boost::RegEx.
423         // if icase is true, then matching is insensitive to case
424         RegexMatch(InfoMap const & m, string const & re, bool icase)
425                 : map_(m), regex_(re, icase) {}
426
427         bool operator()(string const & key) const {
428                 // the data searched is the key + its associated BibTeX/biblio
429                 // fields
430                 string data = key;
431                 InfoMap::const_iterator info = map_.find(key);
432                 if (info != map_.end())
433                         // FIXME UNICODE
434                         data += ' ' + to_utf8(info->second);
435
436                 // Attempts to find a match for the current RE
437                 // somewhere in data.
438                 return boost::regex_search(data, regex_);
439         }
440 private:
441         InfoMap const map_;
442         mutable boost::regex regex_;
443 };
444
445 } // namespace anon
446
447
448 vector<string>::const_iterator
449 searchKeys(InfoMap const & theMap,
450            vector<string> const & keys,
451            string const & search_expr,
452            vector<string>::const_iterator start,
453            Search type,
454            Direction dir,
455            bool caseSensitive)
456 {
457         // Preliminary checks
458         if (start < keys.begin() || start >= keys.end())
459                 return keys.end();
460
461         string expr = trim(search_expr);
462         if (expr.empty())
463                 return keys.end();
464
465         if (type == SIMPLE)
466                 // We must escape special chars in the search_expr so that
467                 // it is treated as a simple string by boost::regex.
468                 expr = escape_special_chars(expr);
469
470         try {
471                 // Build the functor that will be passed to find_if.
472                 RegexMatch const match(theMap, expr, !caseSensitive);
473
474                 // Search the vector of 'keys' from 'start' for one
475                 // that matches the predicate 'match'. Searching can
476                 // be forward or backward from start.
477                 if (dir == FORWARD)
478                         return std::find_if(start, keys.end(), match);
479
480                 vector<string>::const_reverse_iterator rit(start);
481                 vector<string>::const_reverse_iterator rend = keys.rend();
482                 rit = std::find_if(rit, rend, match);
483
484                 if (rit == rend)
485                         return keys.end();
486                 // This is correct and always safe.
487                 // (See Meyer's Effective STL, Item 28.)
488                 return (++rit).base();
489         }
490         catch (boost::regex_error &) {
491                 return keys.end();
492         }
493 }
494
495
496 docstring const parseBibTeX(docstring data, string const & findkey)
497 {
498         // at first we delete all characters right of '%' and
499         // replace tabs through a space and remove leading spaces
500         // we read the data line by line so that the \n are
501         // ignored, too.
502         docstring data_;
503         int Entries = 0;
504         docstring dummy = token(data,'\n', Entries);
505         while (!dummy.empty()) {
506                 // no tabs
507                 dummy = subst(dummy, '\t', ' ');
508                 // no leading spaces
509                 dummy = ltrim(dummy);
510                 // ignore lines with a beginning '%' or ignore all right of %
511                 docstring::size_type const idx =
512                         dummy.empty() ? docstring::npos : dummy.find('%');
513                 if (idx != docstring::npos)
514                         // Check if this is really a comment or just "\%"
515                         if (idx == 0 || dummy[idx - 1] != '\\')
516                                 dummy.erase(idx, docstring::npos);
517                         else
518                                 //  This is "\%", so just erase the '\'
519                                 dummy.erase(idx - 1, 1);
520                 // do we have a new token or a new line of
521                 // the same one? In the first case we ignore
522                 // the \n and in the second we replace it
523                 // with a space
524                 if (!dummy.empty()) {
525                         if (!contains(dummy, '='))
526                                 data_ += ' ' + dummy;
527                         else
528                                 data_ += dummy;
529                 }
530                 dummy = token(data, '\n', ++Entries);
531         }
532
533         // replace double commas with "" for easy scanning
534         data = subst(data_, from_ascii(",,"), from_ascii("\"\""));
535
536         // unlikely!
537         if (data.empty())
538                 return docstring();
539
540         // now get only the important line of the bibtex entry.
541         // all entries are devided by ',' except the last one.
542         data += ',';
543         // now we have same behaviour for all entries because the last one
544         // is "blah ... }"
545         Entries = 0;
546         bool found = false;
547         // parsing of title and booktitle is different from the
548         // others, because booktitle contains title
549         do {
550                 dummy = token(data, ',', Entries++);
551                 if (!dummy.empty()) {
552                         found = contains(ascii_lowercase(dummy), from_ascii(findkey));
553                         if (findkey == "title" &&
554                             contains(ascii_lowercase(dummy), from_ascii("booktitle")))
555                                 found = false;
556                 }
557         } while (!found && !dummy.empty());
558         if (dummy.empty())
559                 // no such keyword
560                 return docstring();
561
562         // we are not sure, if we get all, because "key= "blah, blah" is
563         // allowed.
564         // Therefore we read all until the next "=" character, which follows a
565         // new keyword
566         docstring keyvalue = dummy;
567         dummy = token(data, ',', Entries++);
568         while (!contains(dummy, '=') && !dummy.empty()) {
569                 keyvalue += ',' + dummy;
570                 dummy = token(data, ',', Entries++);
571         }
572
573         // replace double "" with originals ,, (two commas)
574         // leaving us with the all-important line
575         data = subst(keyvalue, from_ascii("\"\""), from_ascii(",,"));
576
577         // Clean-up.
578         // 1. Spaces
579         data = rtrim(data);
580         // 2. if there is no opening '{' then a closing '{' is probably cruft.
581         if (!contains(data, '{'))
582                 data = rtrim(data, "}");
583         // happens, when last keyword
584         docstring::size_type const idx =
585                 !data.empty() ? data.find('=') : docstring::npos;
586
587         if (idx == docstring::npos)
588                 return docstring();
589
590         data = trim(data.substr(idx));
591
592         // a valid entry?
593         if (data.length() < 2 || data[0] != '=')
594                 return docstring();
595         else {
596                 // delete '=' and the following spaces
597                 data = ltrim(data, " =");
598                 if (data.length() < 2) {
599                         // not long enough to find delimiters
600                         return data;
601                 } else {
602                         docstring::size_type keypos = 1;
603                         char_type enclosing;
604                         if (data[0] == '{') {
605                                 enclosing = '}';
606                         } else if (data[0] == '"') {
607                                 enclosing = '"';
608                         } else {
609                                 // no {} and no "", pure data but with a
610                                 // possible ',' at the end
611                                 return rtrim(data, ",");
612                         }
613                         docstring tmp = data.substr(keypos);
614                         while (tmp.find('{') != docstring::npos &&
615                                tmp.find('}') != docstring::npos &&
616                                tmp.find('{') < tmp.find('}') &&
617                                tmp.find('{') < tmp.find(enclosing)) {
618
619                                 keypos += tmp.find('{') + 1;
620                                 tmp = data.substr(keypos);
621                                 keypos += tmp.find('}') + 1;
622                                 tmp = data.substr(keypos);
623                         }
624                         if (tmp.find(enclosing) == docstring::npos)
625                                 return data;
626                         else {
627                                 keypos += tmp.find(enclosing);
628                                 return data.substr(1, keypos - 1);
629                         }
630                 }
631         }
632 }
633
634
635 namespace {
636
637
638 char const * const citeCommands[] = {
639         "cite", "citet", "citep", "citealt", "citealp", "citeauthor",
640         "citeyear", "citeyearpar" };
641
642 unsigned int const nCiteCommands =
643         sizeof(citeCommands) / sizeof(char *);
644
645 CiteStyle const citeStyles[] = {
646         CITE, CITET, CITEP, CITEALT, CITEALP,
647         CITEAUTHOR, CITEYEAR, CITEYEARPAR };
648
649 unsigned int const nCiteStyles =
650         sizeof(citeStyles) / sizeof(CiteStyle);
651
652 CiteStyle const citeStylesFull[] = {
653         CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
654
655 unsigned int const nCiteStylesFull =
656         sizeof(citeStylesFull) / sizeof(CiteStyle);
657
658 CiteStyle const citeStylesUCase[] = {
659         CITET, CITEP, CITEALT, CITEALP, CITEAUTHOR };
660
661 unsigned int const nCiteStylesUCase =
662         sizeof(citeStylesUCase) / sizeof(CiteStyle);
663
664 } // namespace anon
665
666
667 CitationStyle::CitationStyle(string const & command)
668         : style(CITE), full(false), forceUCase(false)
669 {
670         if (command.empty())
671                 return;
672
673         string cmd = command;
674         if (cmd[0] == 'C') {
675                 forceUCase = true;
676                 cmd[0] = 'c';
677         }
678
679         string::size_type const n = cmd.size() - 1;
680         if (cmd != "cite" && cmd[n] == '*') {
681                 full = true;
682                 cmd = cmd.substr(0,n);
683         }
684
685         char const * const * const last = citeCommands + nCiteCommands;
686         char const * const * const ptr = std::find(citeCommands, last, cmd);
687
688         if (ptr != last) {
689                 size_t idx = ptr - citeCommands;
690                 style = citeStyles[idx];
691         }
692 }
693
694
695 string const CitationStyle::asLatexStr() const
696 {
697         string cite = citeCommands[style];
698         if (full) {
699                 CiteStyle const * last = citeStylesFull + nCiteStylesFull;
700                 if (std::find(citeStylesFull, last, style) != last)
701                         cite += '*';
702         }
703
704         if (forceUCase) {
705                 CiteStyle const * last = citeStylesUCase + nCiteStylesUCase;
706                 if (std::find(citeStylesUCase, last, style) != last)
707                         cite[0] = 'C';
708         }
709
710         return cite;
711 }
712
713
714 vector<CiteStyle> const getCiteStyles(CiteEngine const engine)
715 {
716         unsigned int nStyles = 0;
717         unsigned int start = 0;
718
719         switch (engine) {
720         case ENGINE_BASIC:
721                 nStyles = 1;
722                 start = 0;
723                 break;
724         case ENGINE_NATBIB_AUTHORYEAR:
725         case ENGINE_NATBIB_NUMERICAL:
726                 nStyles = nCiteStyles - 1;
727                 start = 1;
728                 break;
729         case ENGINE_JURABIB:
730                 nStyles = nCiteStyles;
731                 start = 0;
732                 break;
733         }
734
735         typedef vector<CiteStyle> cite_vec;
736
737         cite_vec styles(nStyles);
738         cite_vec::size_type i = 0;
739         int j = start;
740         for (; i != styles.size(); ++i, ++j)
741                 styles[i] = citeStyles[j];
742
743         return styles;
744 }
745
746
747 vector<docstring> const
748 getNumericalStrings(string const & key,
749                     InfoMap const & map, vector<CiteStyle> const & styles)
750 {
751         if (map.empty())
752                 return vector<docstring>();
753
754         docstring const author = getAbbreviatedAuthor(map, key);
755         docstring const year   = getYear(map, key);
756         if (author.empty() || year.empty())
757                 return vector<docstring>();
758
759         vector<docstring> vec(styles.size());
760         for (vector<docstring>::size_type i = 0; i != vec.size(); ++i) {
761                 docstring str;
762
763                 switch (styles[i]) {
764                 case CITE:
765                 case CITEP:
766                         str = from_ascii("[#ID]");
767                         break;
768
769                 case CITET:
770                         str = author + " [#ID]";
771                         break;
772
773                 case CITEALT:
774                         str = author + " #ID";
775                         break;
776
777                 case CITEALP:
778                         str = from_ascii("#ID");
779                         break;
780
781                 case CITEAUTHOR:
782                         str = author;
783                         break;
784
785                 case CITEYEAR:
786                         str = year;
787                         break;
788
789                 case CITEYEARPAR:
790                         str = '(' + year + ')';
791                         break;
792                 }
793
794                 vec[i] = str;
795         }
796
797         return vec;
798 }
799
800
801 vector<docstring> const
802 getAuthorYearStrings(string const & key,
803                     InfoMap const & map, vector<CiteStyle> const & styles)
804 {
805         if (map.empty())
806                 return vector<docstring>();
807
808         docstring const author = getAbbreviatedAuthor(map, key);
809         docstring const year   = getYear(map, key);
810         if (author.empty() || year.empty())
811                 return vector<docstring>();
812
813         vector<docstring> vec(styles.size());
814         for (vector<docstring>::size_type i = 0; i != vec.size(); ++i) {
815                 docstring str;
816
817                 switch (styles[i]) {
818                 case CITE:
819                         // jurabib only: Author/Annotator
820                         // (i.e. the "before" field, 2nd opt arg)
821                         str = author + "/<" + _("before") + '>';
822                         break;
823
824                 case CITET:
825                         str = author + " (" + year + ')';
826                         break;
827
828                 case CITEP:
829                         str = '(' + author + ", " + year + ')';
830                         break;
831
832                 case CITEALT:
833                         str = author + ' ' + year ;
834                         break;
835
836                 case CITEALP:
837                         str = author + ", " + year ;
838                         break;
839
840                 case CITEAUTHOR:
841                         str = author;
842                         break;
843
844                 case CITEYEAR:
845                         str = year;
846                         break;
847
848                 case CITEYEARPAR:
849                         str = '(' + year + ')';
850                         break;
851                 }
852
853                 vec[i] = str;
854         }
855
856         return vec;
857 }
858
859 } // namespace biblio
860
861 namespace frontend {
862
863 vector<FamilyPair> const getFamilyData()
864 {
865         vector<FamilyPair> family(5);
866
867         FamilyPair pr;
868
869         pr.first = _("No change");
870         pr.second = Font::IGNORE_FAMILY;
871         family[0] = pr;
872
873         pr.first = _("Roman");
874         pr.second = Font::ROMAN_FAMILY;
875         family[1] = pr;
876
877         pr.first = _("Sans Serif");
878         pr.second = Font::SANS_FAMILY;
879         family[2] = pr;
880
881         pr.first = _("Typewriter");
882         pr.second = Font::TYPEWRITER_FAMILY;
883         family[3] = pr;
884
885         pr.first = _("Reset");
886         pr.second = Font::INHERIT_FAMILY;
887         family[4] = pr;
888
889         return family;
890 }
891
892
893 vector<SeriesPair> const getSeriesData()
894 {
895         vector<SeriesPair> series(4);
896
897         SeriesPair pr;
898
899         pr.first = _("No change");
900         pr.second = Font::IGNORE_SERIES;
901         series[0] = pr;
902
903         pr.first = _("Medium");
904         pr.second = Font::MEDIUM_SERIES;
905         series[1] = pr;
906
907         pr.first = _("Bold");
908         pr.second = Font::BOLD_SERIES;
909         series[2] = pr;
910
911         pr.first = _("Reset");
912         pr.second = Font::INHERIT_SERIES;
913         series[3] = pr;
914
915         return series;
916 }
917
918
919 vector<ShapePair> const getShapeData()
920 {
921         vector<ShapePair> shape(6);
922
923         ShapePair pr;
924
925         pr.first = _("No change");
926         pr.second = Font::IGNORE_SHAPE;
927         shape[0] = pr;
928
929         pr.first = _("Upright");
930         pr.second = Font::UP_SHAPE;
931         shape[1] = pr;
932
933         pr.first = _("Italic");
934         pr.second = Font::ITALIC_SHAPE;
935         shape[2] = pr;
936
937         pr.first = _("Slanted");
938         pr.second = Font::SLANTED_SHAPE;
939         shape[3] = pr;
940
941         pr.first = _("Small Caps");
942         pr.second = Font::SMALLCAPS_SHAPE;
943         shape[4] = pr;
944
945         pr.first = _("Reset");
946         pr.second = Font::INHERIT_SHAPE;
947         shape[5] = pr;
948
949         return shape;
950 }
951
952
953 vector<SizePair> const getSizeData()
954 {
955         vector<SizePair> size(14);
956
957         SizePair pr;
958
959         pr.first = _("No change");
960         pr.second = Font::IGNORE_SIZE;
961         size[0] = pr;
962
963         pr.first = _("Tiny");
964         pr.second = Font::SIZE_TINY;
965         size[1] = pr;
966
967         pr.first = _("Smallest");
968         pr.second = Font::SIZE_SCRIPT;
969         size[2] = pr;
970
971         pr.first = _("Smaller");
972         pr.second = Font::SIZE_FOOTNOTE;
973         size[3] = pr;
974
975         pr.first = _("Small");
976         pr.second = Font::SIZE_SMALL;
977         size[4] = pr;
978
979         pr.first = _("Normal");
980         pr.second = Font::SIZE_NORMAL;
981         size[5] = pr;
982
983         pr.first = _("Large");
984         pr.second = Font::SIZE_LARGE;
985         size[6] = pr;
986
987         pr.first = _("Larger");
988         pr.second = Font::SIZE_LARGER;
989         size[7] = pr;
990
991         pr.first = _("Largest");
992         pr.second = Font::SIZE_LARGEST;
993         size[8] = pr;
994
995         pr.first = _("Huge");
996         pr.second = Font::SIZE_HUGE;
997         size[9] = pr;
998
999         pr.first = _("Huger");
1000         pr.second = Font::SIZE_HUGER;
1001         size[10] = pr;
1002
1003         pr.first = _("Increase");
1004         pr.second = Font::INCREASE_SIZE;
1005         size[11] = pr;
1006
1007         pr.first = _("Decrease");
1008         pr.second = Font::DECREASE_SIZE;
1009         size[12] = pr;
1010
1011         pr.first = _("Reset");
1012         pr.second = Font::INHERIT_SIZE;
1013         size[13] = pr;
1014
1015         return size;
1016 }
1017
1018
1019 vector<BarPair> const getBarData()
1020 {
1021         vector<BarPair> bar(5);
1022
1023         BarPair pr;
1024
1025         pr.first = _("No change");
1026         pr.second = IGNORE;
1027         bar[0] = pr;
1028
1029         pr.first = _("Emph");
1030         pr.second = EMPH_TOGGLE;
1031         bar[1] = pr;
1032
1033         pr.first = _("Underbar");
1034         pr.second = UNDERBAR_TOGGLE;
1035         bar[2] = pr;
1036
1037         pr.first = _("Noun");
1038         pr.second = NOUN_TOGGLE;
1039         bar[3] = pr;
1040
1041         pr.first = _("Reset");
1042         pr.second = INHERIT;
1043         bar[4] = pr;
1044
1045         return bar;
1046 }
1047
1048
1049 vector<ColorPair> const getColorData()
1050 {
1051         vector<ColorPair> color(11);
1052
1053         ColorPair pr;
1054
1055         pr.first = _("No change");
1056         pr.second = Color::ignore;
1057         color[0] = pr;
1058
1059         pr.first = _("No color");
1060         pr.second = Color::none;
1061         color[1] = pr;
1062
1063         pr.first = _("Black");
1064         pr.second = Color::black;
1065         color[2] = pr;
1066
1067         pr.first = _("White");
1068         pr.second = Color::white;
1069         color[3] = pr;
1070
1071         pr.first = _("Red");
1072         pr.second = Color::red;
1073         color[4] = pr;
1074
1075         pr.first = _("Green");
1076         pr.second = Color::green;
1077         color[5] = pr;
1078
1079         pr.first = _("Blue");
1080         pr.second = Color::blue;
1081         color[6] = pr;
1082
1083         pr.first = _("Cyan");
1084         pr.second = Color::cyan;
1085         color[7] = pr;
1086
1087         pr.first = _("Magenta");
1088         pr.second = Color::magenta;
1089         color[8] = pr;
1090
1091         pr.first = _("Yellow");
1092         pr.second = Color::yellow;
1093         color[9] = pr;
1094
1095         pr.first = _("Reset");
1096         pr.second = Color::inherit;
1097         color[10] = pr;
1098
1099         return color;
1100 }
1101
1102
1103
1104 namespace {
1105
1106 class Sorter
1107         : public std::binary_function<LanguagePair,
1108                                       LanguagePair, bool>
1109 {
1110 public:
1111 #if 1//defined(__GNUC__) && (!defined(USE_WCHAR_T) || __GNUC__ < 4)
1112         bool operator()(LanguagePair const & lhs,
1113                         LanguagePair const & rhs) const {
1114                 return lhs.first < rhs.first;
1115         }
1116 #else
1117 // this is supposed to fix bug 2738, but it is not stable yet
1118 // see http://bugzilla.lyx.org/show_bug.cgi?id=2738
1119         Sorter() : loc_("") {};
1120         bool operator()(LanguagePair const & lhs,
1121                         LanguagePair const & rhs) const {
1122                 return loc_(lhs.first, rhs.first);
1123         }
1124 private:
1125         std::locale loc_;
1126 #endif
1127 };
1128
1129 } // namespace anon
1130
1131
1132 vector<LanguagePair> const getLanguageData(bool character_dlg)
1133 {
1134         vector<LanguagePair>::size_type const size = character_dlg ?
1135                 languages.size() + 2 : languages.size();
1136
1137         vector<LanguagePair> langs(size);
1138
1139         if (character_dlg) {
1140                 langs[0].first = _("No change");
1141                 langs[0].second = "ignore";
1142                 langs[1].first = _("Reset");
1143                 langs[1].second = "reset";
1144         }
1145
1146         vector<string>::size_type i = character_dlg ? 2 : 0;
1147         for (Languages::const_iterator cit = languages.begin();
1148              cit != languages.end(); ++cit) {
1149                 langs[i].first  = _(cit->second.display());
1150                 langs[i].second = cit->second.lang();
1151                 ++i;
1152         }
1153
1154         // Don't sort "ignore" and "reset"
1155         vector<LanguagePair>::iterator begin = character_dlg ?
1156                 langs.begin() + 2 : langs.begin();
1157
1158         std::sort(begin, langs.end(), Sorter());
1159
1160         return langs;
1161 }
1162
1163 } // namespace frontend
1164
1165 using support::addName;
1166 using support::FileFilterList;
1167 using support::getExtension;
1168 using support::libFileSearch;
1169 using support::makeAbsPath;
1170 using support::makeRelPath;
1171 using support::onlyFilename;
1172 using support::onlyPath;
1173 using support::package;
1174 using support::prefixIs;
1175 using support::removeExtension;
1176
1177 namespace frontend {
1178
1179
1180 docstring const browseFile(docstring const & filename,
1181                         docstring const & title,
1182                         FileFilterList const & filters,
1183                         bool save,
1184                         pair<docstring,docstring> const & dir1,
1185                         pair<docstring,docstring> const & dir2)
1186 {
1187         docstring lastPath = from_ascii(".");
1188         if (!filename.empty())
1189                 lastPath = from_utf8(onlyPath(to_utf8(filename)));
1190
1191         FileDialog fileDlg(title, LFUN_SELECT_FILE_SYNC, dir1, dir2);
1192
1193         FileDialog::Result result;
1194
1195         if (save)
1196                 result = fileDlg.save(lastPath, filters,
1197                                       from_utf8(onlyFilename(to_utf8(filename))));
1198         else
1199                 result = fileDlg.open(lastPath, filters,
1200                                       from_utf8(onlyFilename(to_utf8(filename))));
1201
1202         return result.second;
1203 }
1204
1205
1206 docstring const browseRelFile(docstring const & filename,
1207                            docstring const & refpath,
1208                            docstring const & title,
1209                            FileFilterList const & filters,
1210                            bool save,
1211                            pair<docstring,docstring> const & dir1,
1212                            pair<docstring,docstring> const & dir2)
1213 {
1214         docstring const fname = from_utf8(makeAbsPath(
1215                 to_utf8(filename), to_utf8(refpath)).absFilename());
1216
1217         docstring const outname = browseFile(fname, title, filters, save,
1218                                           dir1, dir2);
1219         docstring const reloutname = makeRelPath(outname, refpath);
1220         if (prefixIs(reloutname, from_ascii("../")))
1221                 return outname;
1222         else
1223                 return reloutname;
1224 }
1225
1226
1227
1228 docstring const browseLibFile(docstring const & dir,
1229                            docstring const & name,
1230                            docstring const & ext,
1231                            docstring const & title,
1232                            FileFilterList const & filters)
1233 {
1234         // FIXME UNICODE
1235         pair<docstring, docstring> const dir1(_("System files|#S#s"),
1236                                        from_utf8(addName(package().system_support().absFilename(), to_utf8(dir))));
1237
1238         pair<docstring, docstring> const dir2(_("User files|#U#u"),
1239                                        from_utf8(addName(package().user_support().absFilename(), to_utf8(dir))));
1240
1241         docstring const result = browseFile(from_utf8(
1242                 libFileSearch(to_utf8(dir), to_utf8(name), to_utf8(ext)).absFilename()),
1243                 title, filters, false, dir1, dir2);
1244
1245         // remove the extension if it is the default one
1246         docstring noextresult;
1247         if (from_utf8(getExtension(to_utf8(result))) == ext)
1248                 noextresult = from_utf8(removeExtension(to_utf8(result)));
1249         else
1250                 noextresult = result;
1251
1252         // remove the directory, if it is the default one
1253         docstring const file = from_utf8(onlyFilename(to_utf8(noextresult)));
1254         if (from_utf8(libFileSearch(to_utf8(dir), to_utf8(file), to_utf8(ext)).absFilename()) == result)
1255                 return file;
1256         else
1257                 return noextresult;
1258 }
1259
1260
1261 docstring const browseDir(docstring const & pathname,
1262                        docstring const & title,
1263                        pair<docstring,docstring> const & dir1,
1264                        pair<docstring,docstring> const & dir2)
1265 {
1266         docstring lastPath = from_ascii(".");
1267         if (!pathname.empty())
1268                 lastPath = from_utf8(onlyPath(to_utf8(pathname)));
1269
1270         FileDialog fileDlg(title, LFUN_SELECT_FILE_SYNC, dir1, dir2);
1271
1272         FileDialog::Result const result =
1273                 fileDlg.opendir(lastPath, from_utf8(onlyFilename(to_utf8(pathname))));
1274
1275         return result.second;
1276 }
1277
1278
1279 vector<docstring> const getLatexUnits()
1280 {
1281         vector<docstring> units;
1282         int i = 0;
1283         char const * str = stringFromUnit(i);
1284         for (; str != 0; ++i, str = stringFromUnit(i))
1285                 units.push_back(from_ascii(str));
1286
1287         return units;
1288 }
1289
1290 } // namespace frontend
1291
1292
1293 using support::bformat;
1294 using support::contains;
1295 using support::FileName;
1296 using support::getExtension;
1297 using support::getFileContents;
1298 using support::getVectorFromString;
1299 using support::libFileSearch;
1300 using support::onlyFilename;
1301 using support::package;
1302 using support::quoteName;
1303 using support::split;
1304 using support::Systemcall;
1305 using support::token;
1306
1307 namespace frontend {
1308
1309 void rescanTexStyles()
1310 {
1311         // Run rescan in user lyx directory
1312         support::Path p(package().user_support());
1313         FileName const command = libFileSearch("scripts", "TeXFiles.py");
1314         Systemcall one;
1315         int const status = one.startscript(Systemcall::Wait,
1316                         lyx::support::os::python() + ' ' +
1317                         quoteName(command.toFilesystemEncoding()));
1318         if (status == 0)
1319                 return;
1320         // FIXME UNICODE
1321         Alert::error(_("Could not update TeX information"),
1322                      bformat(_("The script `%s' failed."), lyx::from_utf8(command.absFilename())));
1323 }
1324
1325
1326 void texhash()
1327 {
1328         // Run texhash in user lyx directory
1329         support::Path p(package().user_support());
1330
1331         //path to texhash through system
1332         Systemcall one;
1333         one.startscript(Systemcall::Wait,"texhash");
1334 }
1335
1336
1337 void getTexFileList(string const & filename, std::vector<string> & list)
1338 {
1339         list.clear();
1340         FileName const file = libFileSearch("", filename);
1341         if (file.empty())
1342                 return;
1343
1344         list = getVectorFromString(getFileContents(file), "\n");
1345
1346         // Normalise paths like /foo//bar ==> /foo/bar
1347         boost::RegEx regex("/{2,}");
1348         std::vector<string>::iterator it  = list.begin();
1349         std::vector<string>::iterator end = list.end();
1350         for (; it != end; ++it) {
1351                 *it = regex.Merge((*it), "/");
1352         }
1353
1354         // remove empty items and duplicates
1355         list.erase(std::remove(list.begin(), list.end(), ""), list.end());
1356         eliminate_duplicates(list);
1357 }
1358
1359
1360 string const getListOfOptions(string const & classname, string const & type)
1361 {
1362         FileName const filename(getTexFileFromList(classname, type));
1363         if (filename.empty())
1364                 return string();
1365         string optionList = string();
1366         std::ifstream is(filename.toFilesystemEncoding().c_str());
1367         while (is) {
1368                 string s;
1369                 is >> s;
1370                 if (contains(s,"DeclareOption")) {
1371                         s = s.substr(s.find("DeclareOption"));
1372                         s = split(s,'{');               // cut front
1373                         s = token(s,'}',0);             // cut end
1374                         optionList += (s + '\n');
1375                 }
1376         }
1377         return optionList;
1378 }
1379
1380
1381 string const getTexFileFromList(string const & file,
1382                             string const & type)
1383 {
1384         string file_ = file;
1385         // do we need to add the suffix?
1386         if (!(getExtension(file) == type))
1387                 file_ += '.' + type;
1388
1389         lyxerr << "Searching for file " << file_ << endl;
1390
1391         string lstfile;
1392         if (type == "cls")
1393                 lstfile = "clsFiles.lst";
1394         else if (type == "sty")
1395                 lstfile = "styFiles.lst";
1396         else if (type == "bst")
1397                 lstfile = "bstFiles.lst";
1398         else if (type == "bib")
1399                 lstfile = "bibFiles.lst";
1400         FileName const abslstfile = libFileSearch(string(), lstfile);
1401         if (abslstfile.empty()) {
1402                 lyxerr << "File `'" << lstfile << "' not found." << endl;
1403                 return string();
1404         }
1405         string const allClasses = getFileContents(abslstfile);
1406         int entries = 0;
1407         string classfile = token(allClasses, '\n', entries);
1408         int count = 0;
1409         while ((!contains(classfile, file) ||
1410                 (onlyFilename(classfile) != file)) &&
1411                 (++count < 1000)) {
1412                 classfile = token(allClasses, '\n', ++entries);
1413         }
1414
1415         // now we have filename with full path
1416         lyxerr << "with full path: " << classfile << endl;
1417
1418         return classfile;
1419 }
1420
1421 } // namespace frontend
1422 } // namespace lyx