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