]> git.lyx.org Git - lyx.git/blob - src/Format.cpp
listerrors.lyx : Update a link.
[lyx.git] / src / Format.cpp
1 /**
2  * \file Format.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Dekel Tsur
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "Format.h"
14 #include "Buffer.h"
15 #include "BufferParams.h"
16 #include "LyXRC.h"
17 #include "ServerSocket.h"
18
19 #include "frontends/alert.h" //to be removed?
20
21 #include "support/debug.h"
22 #include "support/filetools.h"
23 #include "support/gettext.h"
24 #include "support/lstrings.h"
25 #include "support/os.h"
26 #include "support/Path.h"
27 #include "support/Systemcall.h"
28 #include "support/textutils.h"
29 #include "support/Translator.h"
30
31 #include <algorithm>
32 #include <map>
33 #include <ctime>
34
35 // FIXME: Q_WS_MACX is not available, it's in Qt
36 #ifdef USE_MACOSX_PACKAGING
37 #include "support/linkback/LinkBackProxy.h"
38 #endif
39
40 using namespace std;
41 using namespace lyx::support;
42
43 namespace lyx {
44
45 namespace Alert = frontend::Alert;
46 namespace os = support::os;
47
48 namespace {
49
50 string const token_from_format("$$i");
51 string const token_path_format("$$p");
52 string const token_socket_format("$$a");
53
54
55 class FormatNamesEqual : public unary_function<Format, bool> {
56 public:
57         FormatNamesEqual(string const & name)
58                 : name_(name) {}
59         bool operator()(Format const & f) const
60         {
61                 return f.name() == name_;
62         }
63 private:
64         string name_;
65 };
66
67
68 class FormatExtensionsEqual : public unary_function<Format, bool> {
69 public:
70         FormatExtensionsEqual(string const & extension)
71                 : extension_(extension) {}
72         bool operator()(Format const & f) const
73         {
74                 return f.hasExtension(extension_);
75         }
76 private:
77         string extension_;
78 };
79
80
81 class FormatPrettyNameEqual : public unary_function<Format, bool> {
82 public:
83         FormatPrettyNameEqual(string const & prettyname)
84                 : prettyname_(prettyname) {}
85         bool operator()(Format const & f) const
86         {
87                 return f.prettyname() == prettyname_;
88         }
89 private:
90         string prettyname_;
91 };
92
93 } //namespace anon
94
95
96 bool operator<(Format const & a, Format const & b)
97 {
98         return _(a.prettyname()) < _(b.prettyname());
99 }
100
101
102 Format::Format(string const & n, string const & e, string const & p,
103                string const & s, string const & v, string const & ed,
104                int flags)
105         : name_(n), prettyname_(p), shortcut_(s), viewer_(v),
106           editor_(ed), flags_(flags)
107 {
108         extension_list_ = getVectorFromString(e, ",");
109         LYXERR(Debug::GRAPHICS, "New Format: n=" << n << ", flags=" << flags);
110 }
111
112
113 bool Format::dummy() const
114 {
115         return extension().empty();
116 }
117
118
119 string const Format::extensions() const
120 {
121         return getStringFromVector(extension_list_, ", ");
122 }
123
124
125 bool Format::hasExtension(string const & e) const
126 {
127         return (find(extension_list_.begin(), extension_list_.end(), e)
128                 != extension_list_.end());
129 }
130
131
132 bool Format::isChildFormat() const
133 {
134         if (name_.empty())
135                 return false;
136         return isDigitASCII(name_[name_.length() - 1]);
137 }
138
139
140 string const Format::parentFormat() const
141 {
142         return name_.substr(0, name_.length() - 1);
143 }
144
145
146 void Format::setExtensions(string const & e)
147 {
148         extension_list_ = getVectorFromString(e, ",");
149 }
150
151
152 // This method should return a reference, and throw an exception
153 // if the format named name cannot be found (Lgb)
154 Format const * Formats::getFormat(string const & name) const
155 {
156         FormatList::const_iterator cit =
157                 find_if(formatlist.begin(), formatlist.end(),
158                         FormatNamesEqual(name));
159         if (cit != formatlist.end())
160                 return &(*cit);
161         else
162                 return 0;
163 }
164
165
166 string Formats::getFormatFromFile(FileName const & filename) const
167 {
168         if (filename.empty())
169                 return string();
170
171         string const format = filename.guessFormatFromContents();
172         string const ext = getExtension(filename.absFileName());
173         if ((format == "gzip" || format == "zip" || format == "compress")
174             && !ext.empty()) {
175                 string const & fmt_name = formats.getFormatFromExtension(ext);
176                 if (!fmt_name.empty()) {
177                         Format const * p_format = formats.getFormat(fmt_name);
178                         if (p_format && p_format->zippedNative())
179                                 return p_format->name();
180                 }
181         }
182         if (!format.empty())
183                 return format;
184
185         // try to find a format from the file extension.
186         return getFormatFromExtension(ext);
187 }
188
189
190 string Formats::getFormatFromExtension(string const & ext) const
191 {
192         if (!ext.empty()) {
193                 // this is ambigous if two formats have the same extension,
194                 // but better than nothing
195                 Formats::const_iterator cit =
196                         find_if(formatlist.begin(), formatlist.end(),
197                                 FormatExtensionsEqual(ext));
198                 if (cit != formats.end()) {
199                         LYXERR(Debug::GRAPHICS, "\twill guess format from file extension: "
200                                 << ext << " -> " << cit->name());
201                         return cit->name();
202                 }
203         }
204         return string();
205 }
206
207
208 string Formats::getFormatFromPrettyName(string const & prettyname) const
209 {
210         if (!prettyname.empty()) {
211                 Formats::const_iterator cit =
212                         find_if(formatlist.begin(), formatlist.end(),
213                                 FormatPrettyNameEqual(prettyname));
214                 if (cit != formats.end())
215                         return cit->name();
216         }
217         return string();
218 }
219
220
221 /// Used to store last timestamp of file and whether it is (was) zipped
222 struct ZippedInfo {
223         bool zipped;
224         std::time_t timestamp;
225         ZippedInfo(bool zipped, std::time_t timestamp)
226         : zipped(zipped), timestamp(timestamp) { }
227 };
228
229
230 /// Mapping absolute pathnames of files to their ZippedInfo metadata.
231 static std::map<std::string, ZippedInfo> zipped_;
232
233
234 bool Formats::isZippedFile(support::FileName const & filename) const {
235         string const & fname = filename.absFileName();
236         time_t timestamp = filename.lastModified();
237         map<string, ZippedInfo>::iterator it = zipped_.find(fname);
238         if (it != zipped_.end() && it->second.timestamp == timestamp)
239                 return it->second.zipped;
240         string const & format = getFormatFromFile(filename);
241         bool zipped = (format == "gzip" || format == "zip");
242         zipped_.insert(pair<string, ZippedInfo>(fname, ZippedInfo(zipped, timestamp)));
243         return zipped;
244 }
245
246
247 static string fixCommand(string const & cmd, string const & ext,
248                   os::auto_open_mode mode)
249 {
250         // configure.py says we do not want a viewer/editor
251         if (cmd.empty())
252                 return cmd;
253
254         // Does the OS manage this format?
255         if (os::canAutoOpenFile(ext, mode))
256                 return "auto";
257
258         // if configure.py found nothing, clear the command
259         if (token(cmd, ' ', 0) == "auto")
260                 return string();
261
262         // use the command found by configure.py
263         return cmd;
264 }
265
266
267 void Formats::setAutoOpen()
268 {
269         FormatList::iterator fit = formatlist.begin();
270         FormatList::iterator const fend = formatlist.end();
271         for ( ; fit != fend ; ++fit) {
272                 fit->setViewer(fixCommand(fit->viewer(), fit->extension(), os::VIEW));
273                 fit->setEditor(fixCommand(fit->editor(), fit->extension(), os::EDIT));
274         }
275 }
276
277
278 int Formats::getNumber(string const & name) const
279 {
280         FormatList::const_iterator cit =
281                 find_if(formatlist.begin(), formatlist.end(),
282                         FormatNamesEqual(name));
283         if (cit != formatlist.end())
284                 return distance(formatlist.begin(), cit);
285         else
286                 return -1;
287 }
288
289
290 void Formats::add(string const & name)
291 {
292         if (!getFormat(name))
293                 add(name, name, name, string(), string(), string(),
294                     Format::document);
295 }
296
297
298 void Formats::add(string const & name, string const & extensions,
299                   string const & prettyname, string const & shortcut,
300                   string const & viewer, string const & editor,
301                   int flags)
302 {
303         FormatList::iterator it =
304                 find_if(formatlist.begin(), formatlist.end(),
305                         FormatNamesEqual(name));
306         if (it == formatlist.end())
307                 formatlist.push_back(Format(name, extensions, prettyname,
308                                             shortcut, viewer, editor, flags));
309         else
310                 *it = Format(name, extensions, prettyname, shortcut, viewer,
311                              editor, flags);
312 }
313
314
315 void Formats::erase(string const & name)
316 {
317         FormatList::iterator it =
318                 find_if(formatlist.begin(), formatlist.end(),
319                         FormatNamesEqual(name));
320         if (it != formatlist.end())
321                 formatlist.erase(it);
322 }
323
324
325 void Formats::sort()
326 {
327         std::sort(formatlist.begin(), formatlist.end());
328 }
329
330
331 void Formats::setViewer(string const & name, string const & command)
332 {
333         add(name);
334         FormatList::iterator it =
335                 find_if(formatlist.begin(), formatlist.end(),
336                         FormatNamesEqual(name));
337         if (it != formatlist.end())
338                 it->setViewer(command);
339 }
340
341
342 void Formats::setEditor(string const & name, string const & command)
343 {
344         add(name);
345         FormatList::iterator it =
346                 find_if(formatlist.begin(), formatlist.end(),
347                         FormatNamesEqual(name));
348         if (it != formatlist.end())
349                 it->setEditor(command);
350 }
351
352
353 bool Formats::view(Buffer const & buffer, FileName const & filename,
354                    string const & format_name) const
355 {
356         if (filename.empty() || !filename.exists()) {
357                 Alert::error(_("Cannot view file"),
358                         bformat(_("File does not exist: %1$s"),
359                                 from_utf8(filename.absFileName())));
360                 return false;
361         }
362
363         Format const * format = getFormat(format_name);
364         if (format && format->viewer().empty() &&
365             format->isChildFormat())
366                 format = getFormat(format->parentFormat());
367         if (!format || format->viewer().empty()) {
368 // FIXME: I believe this is the wrong place to show alerts, it should be done
369 // by the caller (this should be "utility" code)
370                 Alert::error(_("Cannot view file"),
371                         bformat(_("No information for viewing %1$s"),
372                                 prettyName(format_name)));
373                 return false;
374         }
375         // viewer is 'auto'
376         if (format->viewer() == "auto") {
377                 if (os::autoOpenFile(filename.absFileName(), os::VIEW, buffer.filePath()))
378                         return true;
379                 else {
380                         Alert::error(_("Cannot view file"),
381                                 bformat(_("Auto-view file %1$s failed"),
382                                         from_utf8(filename.absFileName())));
383                         return false;
384                 }
385         }
386
387         string command = libScriptSearch(format->viewer());
388
389         if (format_name == "dvi" &&
390             !lyxrc.view_dvi_paper_option.empty()) {
391                 string paper_size = buffer.params().paperSizeName(BufferParams::XDVI);
392                 if (!paper_size.empty()) {
393                         command += ' ' + lyxrc.view_dvi_paper_option;
394                         command += ' ' + paper_size;
395                         if (buffer.params().orientation == ORIENTATION_LANDSCAPE &&
396                             buffer.params().papersize != PAPER_CUSTOM)
397                                 command += 'r';
398                 }
399         }
400
401         if (!contains(command, token_from_format))
402                 command += ' ' + token_from_format;
403
404         command = subst(command, token_from_format, quoteName(onlyFileName(filename.toFilesystemEncoding())));
405         command = subst(command, token_path_format, quoteName(onlyPath(filename.toFilesystemEncoding())));
406         command = subst(command, token_socket_format, quoteName(theServerSocket().address()));
407         LYXERR(Debug::FILES, "Executing command: " << command);
408         // FIXME UNICODE utf8 can be wrong for files
409         buffer.message(_("Executing command: ") + from_utf8(command));
410
411         PathChanger p(filename.onlyPath());
412         Systemcall one;
413         one.startscript(Systemcall::DontWait, command, buffer.filePath());
414
415         // we can't report any sort of error, since we aren't waiting
416         return true;
417 }
418
419
420 bool Formats::edit(Buffer const & buffer, FileName const & filename,
421                          string const & format_name) const
422 {
423         if (filename.empty() || !filename.exists()) {
424                 Alert::error(_("Cannot edit file"),
425                         bformat(_("File does not exist: %1$s"),
426                                 from_utf8(filename.absFileName())));
427                 return false;
428         }
429
430         // LinkBack files look like PDF, but have the .linkback extension
431         string const ext = getExtension(filename.absFileName());
432         if (format_name == "pdf" && ext == "linkback") {
433 #ifdef USE_MACOSX_PACKAGING
434                 return editLinkBackFile(filename.absFileName().c_str());
435 #else
436                 Alert::error(_("Cannot edit file"),
437                              _("LinkBack files can only be edited on Apple Mac OSX."));
438                 return false;
439 #endif // USE_MACOSX_PACKAGING
440         }
441
442         Format const * format = getFormat(format_name);
443         if (format && format->editor().empty() &&
444             format->isChildFormat())
445                 format = getFormat(format->parentFormat());
446         if (!format || format->editor().empty()) {
447 // FIXME: I believe this is the wrong place to show alerts, it should
448 // be done by the caller (this should be "utility" code)
449                 Alert::error(_("Cannot edit file"),
450                         bformat(_("No information for editing %1$s"),
451                                 prettyName(format_name)));
452                 return false;
453         }
454
455         // editor is 'auto'
456         if (format->editor() == "auto") {
457                 if (os::autoOpenFile(filename.absFileName(), os::EDIT, buffer.filePath()))
458                         return true;
459                 else {
460                         Alert::error(_("Cannot edit file"),
461                                 bformat(_("Auto-edit file %1$s failed"),
462                                         from_utf8(filename.absFileName())));
463                         return false;
464                 }
465         }
466
467         string command = format->editor();
468
469         if (!contains(command, token_from_format))
470                 command += ' ' + token_from_format;
471
472         command = subst(command, token_from_format, quoteName(filename.toFilesystemEncoding()));
473         command = subst(command, token_path_format, quoteName(onlyPath(filename.toFilesystemEncoding())));
474         command = subst(command, token_socket_format, quoteName(theServerSocket().address()));
475         LYXERR(Debug::FILES, "Executing command: " << command);
476         // FIXME UNICODE utf8 can be wrong for files
477         buffer.message(_("Executing command: ") + from_utf8(command));
478
479         Systemcall one;
480         one.startscript(Systemcall::DontWait, command, buffer.filePath());
481
482         // we can't report any sort of error, since we aren't waiting
483         return true;
484 }
485
486
487 docstring const Formats::prettyName(string const & name) const
488 {
489         Format const * format = getFormat(name);
490         if (format)
491                 return from_utf8(format->prettyname());
492         else
493                 return from_utf8(name);
494 }
495
496
497 string const Formats::extension(string const & name) const
498 {
499         Format const * format = getFormat(name);
500         if (format)
501                 return format->extension();
502         else
503                 return name;
504 }
505
506
507 string const Formats::extensions(string const & name) const
508 {
509         Format const * format = getFormat(name);
510         if (format)
511                 return format->extensions();
512         else
513                 return name;
514 }
515
516
517 namespace {
518 typedef Translator<OutputParams::FLAVOR, string> FlavorTranslator;
519
520 FlavorTranslator initFlavorTranslator()
521 {
522         FlavorTranslator f(OutputParams::LATEX, "latex");
523         f.addPair(OutputParams::DVILUATEX, "dviluatex");
524         f.addPair(OutputParams::LUATEX, "luatex");
525         f.addPair(OutputParams::PDFLATEX, "pdflatex");
526         f.addPair(OutputParams::XETEX, "xetex");
527         f.addPair(OutputParams::XML, "docbook-xml");
528         f.addPair(OutputParams::HTML, "xhtml");
529         f.addPair(OutputParams::TEXT, "text");
530         return f;
531 }
532
533
534 FlavorTranslator const & flavorTranslator()
535 {
536         static FlavorTranslator translator = initFlavorTranslator();
537         return translator;
538 }
539 }
540
541
542 std::string flavor2format(OutputParams::FLAVOR flavor)
543 {
544         return flavorTranslator().find(flavor);
545 }
546
547
548 /* Not currently needed, but I'll leave the code in case it is.
549 OutputParams::FLAVOR format2flavor(std::string fmt)
550 {
551         return flavorTranslator().find(fmt);
552 } */
553
554 Formats formats;
555
556 Formats system_formats;
557
558
559 } // namespace lyx