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