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