]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
use the lyxdot replacement mechanism also in insetexternal
[lyx.git] / src / support / filetools.C
1 /**
2  * \file filetools.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * parts Copyright 1985, 1990, 1993 Free Software Foundation, Inc.
7  *
8  * \author Ivan Schreter
9  * \author Dirk Niggemann
10  * \author Asger Alstrup
11  * \author Lars Gullik Bjønnes
12  * \author Jean-Marc Lasgouttes
13  * \author Angus Leeming
14  * \author John Levon
15  * \author Herbert Voß
16  *
17  * Full author contact details are available in file CREDITS.
18  *
19  * General path-mangling functions
20  */
21
22 #include <config.h>
23
24 #include "support/convert.h"
25 #include "support/environment.h"
26 #include "support/filetools.h"
27 #include "support/fs_extras.h"
28 #include "support/lstrings.h"
29 #include "support/lyxlib.h"
30 #include "support/os.h"
31 #include "support/package.h"
32 #include "support/path.h"
33 #include "support/systemcall.h"
34
35 // FIXME Interface violation
36 #include "gettext.h"
37 #include "debug.h"
38
39 #include <boost/assert.hpp>
40 #include <boost/filesystem/operations.hpp>
41 #include <boost/regex.hpp>
42
43 #include <fcntl.h>
44
45 #include <cctype>
46 #include <cerrno>
47 #include <cstdlib>
48 #include <cstdio>
49
50 #include <utility>
51 #include <fstream>
52 #include <sstream>
53
54 #ifndef CXX_GLOBAL_CSTD
55 using std::fgetc;
56 using std::isalnum;
57 using std::isalpha;
58 #endif
59
60 using std::endl;
61 using std::getline;
62 using std::make_pair;
63 using std::string;
64 using std::ifstream;
65 using std::ostringstream;
66 using std::vector;
67
68 namespace fs = boost::filesystem;
69
70 namespace lyx {
71 namespace support {
72
73 bool IsLyXFilename(string const & filename)
74 {
75         return suffixIs(ascii_lowercase(filename), ".lyx");
76 }
77
78
79 bool IsSGMLFilename(string const & filename)
80 {
81         return suffixIs(ascii_lowercase(filename), ".sgml");
82 }
83
84
85 string const latex_path(string const & original_path,
86                 latex_path_extension extension,
87                 latex_path_dots dots)
88 {
89         string path = subst(original_path, "\\", "/");
90         path = subst(path, "~", "\\string~");
91         if (path.find(' ') != string::npos)
92                 // We can't use '"' because " is sometimes active (e.g. if
93                 // babel is loaded with the "german" option)
94                 if (extension == EXCLUDE_EXTENSION) {
95                         string const base = ChangeExtension(path, string());
96                         string const ext = GetExtension(path);
97                         // ChangeExtension calls os::internal_path internally
98                         // so don't use it to re-add the extension.
99                         path = "\\string\"" + base + "\\string\"." + ext;
100                 } else
101                         path = "\\string\"" + path + "\\string\"";
102         if (dots == ESCAPE_DOTS)
103                 return subst(path, ".", "\\lyxdot ");
104         return path;
105 }
106
107
108 // Substitutes spaces with underscores in filename (and path)
109 string const MakeLatexName(string const & file)
110 {
111         string name = OnlyFilename(file);
112         string const path = OnlyPath(file);
113
114         for (string::size_type i = 0; i < name.length(); ++i) {
115                 name[i] &= 0x7f; // set 8th bit to 0
116         };
117
118         // ok so we scan through the string twice, but who cares.
119         string const keep("abcdefghijklmnopqrstuvwxyz"
120                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
121                 "@!\"'()*+,-./0123456789:;<=>?[]`|");
122
123         string::size_type pos = 0;
124         while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
125                 name[pos++] = '_';
126         }
127         return AddName(path, name);
128 }
129
130
131 string const QuoteName(string const & name)
132 {
133         return (os::shell() == os::UNIX) ?
134                 '\'' + name + '\'':
135                 '"' + name + '"';
136 }
137
138
139 // Is a file readable ?
140 bool IsFileReadable(string const & path)
141 {
142         return fs::exists(path) && !fs::is_directory(path) && fs::is_readable(path);
143 }
144
145
146 //returns true: dir writeable
147 //        false: not writeable
148 bool IsDirWriteable(string const & path)
149 {
150         lyxerr[Debug::FILES] << "IsDirWriteable: " << path << endl;
151
152         string const tmpfl(tempName(path, "lyxwritetest"));
153
154         if (tmpfl.empty())
155                 return false;
156
157         unlink(tmpfl);
158         return true;
159 }
160
161
162 // Uses a string of paths separated by ";"s to find a file to open.
163 // Can't cope with pathnames with a ';' in them. Returns full path to file.
164 // If path entry begins with $$LyX/, use system_lyxdir
165 // If path entry begins with $$User/, use user_lyxdir
166 // Example: "$$User/doc;$$LyX/doc"
167 string const FileOpenSearch(string const & path, string const & name,
168                              string const & ext)
169 {
170         string real_file;
171         string path_element;
172         bool notfound = true;
173         string tmppath = split(path, path_element, ';');
174
175         while (notfound && !path_element.empty()) {
176                 path_element = os::internal_path(path_element);
177                 if (!suffixIs(path_element, '/'))
178                         path_element+= '/';
179                 path_element = subst(path_element, "$$LyX",
180                                      package().system_support());
181                 path_element = subst(path_element, "$$User",
182                                      package().user_support());
183
184                 real_file = FileSearch(path_element, name, ext);
185
186                 if (real_file.empty()) {
187                         do {
188                                 tmppath = split(tmppath, path_element, ';');
189                         } while (!tmppath.empty() && path_element.empty());
190                 } else {
191                         notfound = false;
192                 }
193         }
194 #ifdef __EMX__
195         if (ext.empty() && notfound) {
196                 real_file = FileOpenSearch(path, name, "exe");
197                 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
198         }
199 #endif
200         return real_file;
201 }
202
203
204 /// Returns a vector of all files in directory dir having extension ext.
205 vector<string> const DirList(string const & dir, string const & ext)
206 {
207         // EXCEPTIONS FIXME. Rewrite needed when we turn on exceptions. (Lgb)
208         vector<string> dirlist;
209
210         if (!(fs::exists(dir) && fs::is_directory(dir))) {
211                 lyxerr[Debug::FILES]
212                         << "Directory \"" << dir
213                         << "\" does not exist to DirList." << endl;
214                 return dirlist;
215         }
216
217         string extension;
218         if (!ext.empty() && ext[0] != '.')
219                 extension += '.';
220         extension += ext;
221
222         fs::directory_iterator dit(dir);
223         fs::directory_iterator end;
224         for (; dit != end; ++dit) {
225                 string const & fil = dit->leaf();
226                 if (suffixIs(fil, extension)) {
227                         dirlist.push_back(fil);
228                 }
229         }
230         return dirlist;
231 }
232
233
234 // Returns the real name of file name in directory path, with optional
235 // extension ext.
236 string const FileSearch(string const & path, string const & name,
237                         string const & ext)
238 {
239         // if `name' is an absolute path, we ignore the setting of `path'
240         // Expand Environmentvariables in 'name'
241         string const tmpname = ReplaceEnvironmentPath(name);
242         string fullname = MakeAbsPath(tmpname, path);
243         // search first without extension, then with it.
244         if (IsFileReadable(fullname))
245                 return fullname;
246         else if (ext.empty())
247                 return string();
248         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
249                 fullname += '.';
250                 fullname += ext;
251                 if (IsFileReadable(fullname))
252                         return fullname;
253                 else
254                         return string();
255         }
256 }
257
258
259 // Search the file name.ext in the subdirectory dir of
260 //   1) user_lyxdir
261 //   2) build_lyxdir (if not empty)
262 //   3) system_lyxdir
263 string const LibFileSearch(string const & dir, string const & name,
264                            string const & ext)
265 {
266         string fullname = FileSearch(AddPath(package().user_support(), dir),
267                                      name, ext);
268         if (!fullname.empty())
269                 return fullname;
270
271         if (!package().build_support().empty())
272                 fullname = FileSearch(AddPath(package().build_support(), dir),
273                                       name, ext);
274         if (!fullname.empty())
275                 return fullname;
276
277         return FileSearch(AddPath(package().system_support(), dir), name, ext);
278 }
279
280
281 string const
282 i18nLibFileSearch(string const & dir, string const & name,
283                   string const & ext)
284 {
285         // the following comments are from intl/dcigettext.c. We try
286         // to mimick this behaviour here.
287         /* The highest priority value is the `LANGUAGE' environment
288            variable. But we don't use the value if the currently
289            selected locale is the C locale. This is a GNU extension. */
290         /* [Otherwise] We have to proceed with the POSIX methods of
291            looking to `LC_ALL', `LC_xxx', and `LANG'. */
292
293         string lang = getEnv("LC_ALL");
294         if (lang.empty()) {
295                 lang = getEnv("LC_MESSAGES");
296                 if (lang.empty()) {
297                         lang = getEnv("LANG");
298                         if (lang.empty())
299                                 lang = "C";
300                 }
301         }
302
303         string const language = getEnv("LANGUAGE");
304         if (lang != "C" && lang != "POSIX" && !language.empty())
305                 lang = language;
306
307         string l;
308         lang = split(lang, l, ':');
309         while (!l.empty() && l != "C" && l != "POSIX") {
310                 string const tmp = LibFileSearch(dir,
311                                                  token(l, '_', 0) + '_' + name,
312                                                  ext);
313                 if (!tmp.empty())
314                         return tmp;
315                 lang = split(lang, l, ':');
316         }
317
318         return LibFileSearch(dir, name, ext);
319 }
320
321
322 string const LibScriptSearch(string const & command_in)
323 {
324         string const token_scriptpath("$$s/");
325
326         string command = command_in;
327         // Find the starting position of "$$s/"
328         string::size_type const pos1 = command.find(token_scriptpath);
329         if (pos1 == string::npos)
330                 return command;
331         // Find the end of the "$$s/some_subdir/some_script" word within
332         // command. Assumes that the script name does not contain spaces.
333         string::size_type const start_script = pos1 + 4;
334         string::size_type const pos2 = command.find(' ', start_script);
335         string::size_type const size_script = pos2 == string::npos?
336                 (command.size() - start_script) : pos2 - start_script;
337
338         // Does this script file exist?
339         string const script =
340                 LibFileSearch(".", command.substr(start_script, size_script));
341
342         if (script.empty()) {
343                 // Replace "$$s/" with ""
344                 command.erase(pos1, 4);
345         } else {
346                 // Replace "$$s/foo/some_script" with "<path to>/some_script".
347                 string::size_type const size_replace = size_script + 4;
348                 command.replace(pos1, size_replace, QuoteName(script));
349         }
350
351         return command;
352 }
353
354
355 namespace {
356
357 string const createTmpDir(string const & tempdir, string const & mask)
358 {
359         lyxerr[Debug::FILES]
360                 << "createTmpDir: tempdir=`" << tempdir << "'\n"
361                 << "createTmpDir:    mask=`" << mask << '\'' << endl;
362
363         string const tmpfl(tempName(tempdir, mask));
364         // lyx::tempName actually creates a file to make sure that it
365         // stays unique. So we have to delete it before we can create
366         // a dir with the same name. Note also that we are not thread
367         // safe because of the gap between unlink and mkdir. (Lgb)
368         unlink(tmpfl);
369
370         if (tmpfl.empty() || mkdir(tmpfl, 0700)) {
371                 lyxerr << "LyX could not create the temporary directory '"
372                        << tmpfl << "'" << endl;
373                 return string();
374         }
375
376         return MakeAbsPath(tmpfl);
377 }
378
379 } // namespace anon
380
381
382 bool destroyDir(string const & tmpdir)
383 {
384
385 #ifdef __EMX__
386         Path p(user_lyxdir());
387 #endif
388         return fs::remove_all(tmpdir) > 0;
389 }
390
391
392 string const createBufferTmpDir()
393 {
394         static int count;
395         // We are in our own directory.  Why bother to mangle name?
396         // In fact I wrote this code to circumvent a problematic behaviour
397         // (bug?) of EMX mkstemp().
398         string const tmpfl =
399                 package().temp_dir() + "/lyx_tmpbuf" +
400                 convert<string>(count++);
401
402         if (mkdir(tmpfl, 0777)) {
403                 lyxerr << "LyX could not create the temporary directory '"
404                        << tmpfl << "'" << endl;
405                 return string();
406         }
407         return tmpfl;
408 }
409
410
411 string const createLyXTmpDir(string const & deflt)
412 {
413         if (!deflt.empty() && deflt != "/tmp") {
414                 if (mkdir(deflt, 0777)) {
415 #ifdef __EMX__
416                         Path p(package().user_support());
417 #endif
418                         if (IsDirWriteable(deflt)) {
419                                 // deflt could not be created because it
420                                 // did exist already, so let's create our own
421                                 // dir inside deflt.
422                                 return createTmpDir(deflt, "lyx_tmpdir");
423                         } else {
424                                 // some other error occured.
425                                 return createTmpDir("/tmp", "lyx_tmpdir");
426                         }
427                 } else
428                         return deflt;
429         } else {
430 #ifdef __EMX__
431                 Path p(package().user_support());
432 #endif
433                 return createTmpDir("/tmp", "lyx_tmpdir");
434         }
435 }
436
437
438 bool createDirectory(string const & path, int permission)
439 {
440         string temp(rtrim(os::internal_path(path), "/"));
441
442         BOOST_ASSERT(!temp.empty());
443
444         if (mkdir(temp, permission))
445                 return false;
446
447         return true;
448 }
449
450
451 // Strip filename from path name
452 string const OnlyPath(string const & Filename)
453 {
454         // If empty filename, return empty
455         if (Filename.empty()) return Filename;
456
457         // Find last / or start of filename
458         string::size_type j = Filename.rfind('/');
459         if (j == string::npos)
460                 return "./";
461         return Filename.substr(0, j + 1);
462 }
463
464
465 // Convert relative path into absolute path based on a basepath.
466 // If relpath is absolute, just use that.
467 // If basepath is empty, use CWD as base.
468 string const MakeAbsPath(string const & RelPath, string const & BasePath)
469 {
470         // checks for already absolute path
471         if (os::is_absolute_path(RelPath))
472                 return RelPath;
473
474         // Copies given paths
475         string TempRel(os::internal_path(RelPath));
476         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
477         TempRel = subst(TempRel, "//", "/");
478
479         string TempBase;
480
481         if (os::is_absolute_path(BasePath))
482                 TempBase = BasePath;
483         else
484                 TempBase = AddPath(getcwd(), BasePath);
485
486         // Handle /./ at the end of the path
487         while (suffixIs(TempBase, "/./"))
488                 TempBase.erase(TempBase.length() - 2);
489
490         // processes relative path
491         string RTemp(TempRel);
492         string Temp;
493
494         while (!RTemp.empty()) {
495                 // Split by next /
496                 RTemp = split(RTemp, Temp, '/');
497
498                 if (Temp == ".") continue;
499                 if (Temp == "..") {
500                         // Remove one level of TempBase
501                         string::difference_type i = TempBase.length() - 2;
502 #ifndef __EMX__
503                         if (i < 0) i = 0;
504                         while (i > 0 && TempBase[i] != '/') --i;
505                         if (i > 0)
506 #else
507                         if (i < 2) i = 2;
508                         while (i > 2 && TempBase[i] != '/') --i;
509                         if (i > 2)
510 #endif
511                                 TempBase.erase(i, string::npos);
512                         else
513                                 TempBase += '/';
514                 } else if (Temp.empty() && !RTemp.empty()) {
515                                 TempBase = os::current_root() + RTemp;
516                                 RTemp.erase();
517                 } else {
518                         // Add this piece to TempBase
519                         if (!suffixIs(TempBase, '/'))
520                                 TempBase += '/';
521                         TempBase += Temp;
522                 }
523         }
524
525         // returns absolute path
526         return os::internal_path(TempBase);
527 }
528
529
530 // Correctly append filename to the pathname.
531 // If pathname is '.', then don't use pathname.
532 // Chops any path of filename.
533 string const AddName(string const & path, string const & fname)
534 {
535         // Get basename
536         string const basename(OnlyFilename(fname));
537
538         string buf;
539
540         if (path != "." && path != "./" && !path.empty()) {
541                 buf = os::internal_path(path);
542                 if (!suffixIs(path, '/'))
543                         buf += '/';
544         }
545
546         return buf + basename;
547 }
548
549
550 // Strips path from filename
551 string const OnlyFilename(string const & fname)
552 {
553         if (fname.empty())
554                 return fname;
555
556         string::size_type j = fname.rfind('/');
557         if (j == string::npos) // no '/' in fname
558                 return fname;
559
560         // Strip to basename
561         return fname.substr(j + 1);
562 }
563
564
565 /// Returns true is path is absolute
566 bool AbsolutePath(string const & path)
567 {
568         return os::is_absolute_path(path);
569 }
570
571
572
573 // Create absolute path. If impossible, don't do anything
574 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
575 string const ExpandPath(string const & path)
576 {
577         // checks for already absolute path
578         string RTemp(ReplaceEnvironmentPath(path));
579         if (os::is_absolute_path(RTemp))
580                 return RTemp;
581
582         string Temp;
583         string const copy(RTemp);
584
585         // Split by next /
586         RTemp = split(RTemp, Temp, '/');
587
588         if (Temp == ".") {
589                 return getcwd() + '/' + RTemp;
590         }
591         if (Temp == "~") {
592                 return package().home_dir() + '/' + RTemp;
593         }
594         if (Temp == "..") {
595                 return MakeAbsPath(copy);
596         }
597         // Don't know how to handle this
598         return copy;
599 }
600
601
602 // Normalize a path
603 // Constracts path/../path
604 // Can't handle "../../" or "/../" (Asger)
605 // Also converts paths like /foo//bar ==> /foo/bar
606 string const NormalizePath(string const & path)
607 {
608         string TempBase;
609         string RTemp;
610         string Temp;
611
612         if (os::is_absolute_path(path))
613                 RTemp = path;
614         else
615                 // Make implicit current directory explicit
616                 RTemp = "./" +path;
617
618         // Normalise paths like /foo//bar ==> /foo/bar
619         boost::RegEx regex("/{2,}");
620         RTemp = regex.Merge(RTemp, "/");
621
622         while (!RTemp.empty()) {
623                 // Split by next /
624                 RTemp = split(RTemp, Temp, '/');
625
626                 if (Temp == ".") {
627                         TempBase = "./";
628                 } else if (Temp == "..") {
629                         // Remove one level of TempBase
630                         string::difference_type i = TempBase.length() - 2;
631                         while (i > 0 && TempBase[i] != '/')
632                                 --i;
633                         if (i >= 0 && TempBase[i] == '/')
634                                 TempBase.erase(i + 1, string::npos);
635                         else
636                                 TempBase = "../";
637                 } else {
638                         TempBase += Temp + '/';
639                 }
640         }
641
642         // returns absolute path
643         return TempBase;
644 }
645
646
647 string const GetFileContents(string const & fname)
648 {
649         if (fs::exists(fname)) {
650                 ifstream ifs(fname.c_str());
651                 ostringstream ofs;
652                 if (ifs && ofs) {
653                         ofs << ifs.rdbuf();
654                         ifs.close();
655                         return ofs.str();
656                 }
657         }
658         lyxerr << "LyX was not able to read file '" << fname << '\'' << endl;
659         return string();
660 }
661
662
663 // Search the string for ${VAR} and $VAR and replace VAR using getenv.
664 string const ReplaceEnvironmentPath(string const & path)
665 {
666         // ${VAR} is defined as
667         // $\{[A-Za-z_][A-Za-z_0-9]*\}
668         static string const envvar_br = "[$]\\{([A-Za-z_][A-Za-z_0-9]*)\\}";
669
670         // $VAR is defined as:
671         // $\{[A-Za-z_][A-Za-z_0-9]*\}
672         static string const envvar = "[$]([A-Za-z_][A-Za-z_0-9]*)";
673
674         static boost::regex envvar_br_re("(.*)" + envvar_br + "(.*)");
675         static boost::regex envvar_re("(.*)" + envvar + "(.*)");
676         boost::smatch what;
677
678         string result = path;
679         while (1) {
680                 regex_match(result, what, envvar_br_re);
681                 if (!what[0].matched) {
682                         regex_match(result, what, envvar_re);
683                         if (!what[0].matched)
684                                 break;
685                 }
686                 result = what.str(1) + getEnv(what.str(2)) + what.str(3);
687         }
688         return result;
689 }
690
691
692 // Make relative path out of two absolute paths
693 string const MakeRelPath(string const & abspath, string const & basepath)
694 // Makes relative path out of absolute path. If it is deeper than basepath,
695 // it's easy. If basepath and abspath share something (they are all deeper
696 // than some directory), it'll be rendered using ..'s. If they are completely
697 // different, then the absolute path will be used as relative path.
698 {
699         string::size_type const abslen = abspath.length();
700         string::size_type const baselen = basepath.length();
701
702         string::size_type i = os::common_path(abspath, basepath);
703
704         if (i == 0) {
705                 // actually no match - cannot make it relative
706                 return abspath;
707         }
708
709         // Count how many dirs there are in basepath above match
710         // and append as many '..''s into relpath
711         string buf;
712         string::size_type j = i;
713         while (j < baselen) {
714                 if (basepath[j] == '/') {
715                         if (j + 1 == baselen)
716                                 break;
717                         buf += "../";
718                 }
719                 ++j;
720         }
721
722         // Append relative stuff from common directory to abspath
723         if (abspath[i] == '/')
724                 ++i;
725         for (; i < abslen; ++i)
726                 buf += abspath[i];
727         // Remove trailing /
728         if (suffixIs(buf, '/'))
729                 buf.erase(buf.length() - 1);
730         // Substitute empty with .
731         if (buf.empty())
732                 buf = '.';
733         return buf;
734 }
735
736
737 // Append sub-directory(ies) to a path in an intelligent way
738 string const AddPath(string const & path, string const & path_2)
739 {
740         string buf;
741         string const path2 = os::internal_path(path_2);
742
743         if (!path.empty() && path != "." && path != "./") {
744                 buf = os::internal_path(path);
745                 if (path[path.length() - 1] != '/')
746                         buf += '/';
747         }
748
749         if (!path2.empty()) {
750                 string::size_type const p2start = path2.find_first_not_of('/');
751                 string::size_type const p2end = path2.find_last_not_of('/');
752                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
753                 buf += tmp + '/';
754         }
755         return buf;
756 }
757
758
759 /*
760  Change extension of oldname to extension.
761  Strips path off if no_path == true.
762  If no extension on oldname, just appends.
763  */
764 string const ChangeExtension(string const & oldname, string const & extension)
765 {
766         string::size_type const last_slash = oldname.rfind('/');
767         string::size_type last_dot = oldname.rfind('.');
768         if (last_dot < last_slash && last_slash != string::npos)
769                 last_dot = string::npos;
770
771         string ext;
772         // Make sure the extension starts with a dot
773         if (!extension.empty() && extension[0] != '.')
774                 ext= '.' + extension;
775         else
776                 ext = extension;
777
778         return os::internal_path(oldname.substr(0, last_dot) + ext);
779 }
780
781
782 /// Return the extension of the file (not including the .)
783 string const GetExtension(string const & name)
784 {
785         string::size_type const last_slash = name.rfind('/');
786         string::size_type const last_dot = name.rfind('.');
787         if (last_dot != string::npos &&
788             (last_slash == string::npos || last_dot > last_slash))
789                 return name.substr(last_dot + 1,
790                                    name.length() - (last_dot + 1));
791         else
792                 return string();
793 }
794
795
796 // the different filetypes and what they contain in one of the first lines
797 // (dots are any characters).           (Herbert 20020131)
798 // AGR  Grace...
799 // BMP  BM...
800 // EPS  %!PS-Adobe-3.0 EPSF...
801 // FIG  #FIG...
802 // FITS ...BITPIX...
803 // GIF  GIF...
804 // JPG  JFIF
805 // PDF  %PDF-...
806 // PNG  .PNG...
807 // PBM  P1... or P4     (B/W)
808 // PGM  P2... or P5     (Grayscale)
809 // PPM  P3... or P6     (color)
810 // PS   %!PS-Adobe-2.0 or 1.0,  no "EPSF"!
811 // SGI  \001\332...     (decimal 474)
812 // TGIF %TGIF...
813 // TIFF II... or MM...
814 // XBM  ..._bits[]...
815 // XPM  /* XPM */    sometimes missing (f.ex. tgif-export)
816 //      ...static char *...
817 // XWD  \000\000\000\151        (0x00006900) decimal 105
818 //
819 // GZIP \037\213        http://www.ietf.org/rfc/rfc1952.txt
820 // ZIP  PK...                   http://www.halyava.ru/document/ind_arch.htm
821 // Z    \037\235                UNIX compress
822
823 string const getFormatFromContents(string const & filename)
824 {
825         // paranoia check
826         if (filename.empty() || !IsFileReadable(filename))
827                 return string();
828
829         ifstream ifs(filename.c_str());
830         if (!ifs)
831                 // Couldn't open file...
832                 return string();
833
834         // gnuzip
835         string const gzipStamp = "\037\213";
836
837         // PKZIP
838         string const zipStamp = "PK";
839
840         // compress
841         string const compressStamp = "\037\235";
842
843         // Maximum strings to read
844         int const max_count = 50;
845         int count = 0;
846
847         string str;
848         string format;
849         bool firstLine = true;
850         while ((count++ < max_count) && format.empty()) {
851                 if (ifs.eof()) {
852                         lyxerr[Debug::GRAPHICS]
853                                 << "filetools(getFormatFromContents)\n"
854                                 << "\tFile type not recognised before EOF!"
855                                 << endl;
856                         break;
857                 }
858
859                 getline(ifs, str);
860                 string const stamp = str.substr(0,2);
861                 if (firstLine && str.size() >= 2) {
862                         // at first we check for a zipped file, because this
863                         // information is saved in the first bytes of the file!
864                         // also some graphic formats which save the information
865                         // in the first line, too.
866                         if (prefixIs(str, gzipStamp)) {
867                                 format =  "gzip";
868
869                         } else if (stamp == zipStamp) {
870                                 format =  "zip";
871
872                         } else if (stamp == compressStamp) {
873                                 format =  "compress";
874
875                         // the graphics part
876                         } else if (stamp == "BM") {
877                                 format =  "bmp";
878
879                         } else if (stamp == "\001\332") {
880                                 format =  "sgi";
881
882                         // PBM family
883                         // Don't need to use str.at(0), str.at(1) because
884                         // we already know that str.size() >= 2
885                         } else if (str[0] == 'P') {
886                                 switch (str[1]) {
887                                 case '1':
888                                 case '4':
889                                         format =  "pbm";
890                                     break;
891                                 case '2':
892                                 case '5':
893                                         format =  "pgm";
894                                     break;
895                                 case '3':
896                                 case '6':
897                                         format =  "ppm";
898                                 }
899                                 break;
900
901                         } else if ((stamp == "II") || (stamp == "MM")) {
902                                 format =  "tiff";
903
904                         } else if (prefixIs(str,"%TGIF")) {
905                                 format =  "tgif";
906
907                         } else if (prefixIs(str,"#FIG")) {
908                                 format =  "fig";
909
910                         } else if (prefixIs(str,"GIF")) {
911                                 format =  "gif";
912
913                         } else if (str.size() > 3) {
914                                 int const c = ((str[0] << 24) & (str[1] << 16) &
915                                                (str[2] << 8)  & str[3]);
916                                 if (c == 105) {
917                                         format =  "xwd";
918                                 }
919                         }
920
921                         firstLine = false;
922                 }
923
924                 if (!format.empty())
925                     break;
926                 else if (contains(str,"EPSF"))
927                         // dummy, if we have wrong file description like
928                         // %!PS-Adobe-2.0EPSF"
929                         format =  "eps";
930
931                 else if (contains(str,"Grace"))
932                         format =  "agr";
933
934                 else if (contains(str,"JFIF"))
935                         format =  "jpg";
936
937                 else if (contains(str,"%PDF"))
938                         format =  "pdf";
939
940                 else if (contains(str,"PNG"))
941                         format =  "png";
942
943                 else if (contains(str,"%!PS-Adobe")) {
944                         // eps or ps
945                         ifs >> str;
946                         if (contains(str,"EPSF"))
947                                 format = "eps";
948                         else
949                             format = "ps";
950                 }
951
952                 else if (contains(str,"_bits[]"))
953                         format = "xbm";
954
955                 else if (contains(str,"XPM") || contains(str, "static char *"))
956                         format = "xpm";
957
958                 else if (contains(str,"BITPIX"))
959                         format = "fits";
960         }
961
962         if (!format.empty()) {
963                 lyxerr[Debug::GRAPHICS]
964                         << "Recognised Fileformat: " << format << endl;
965                 return format;
966         }
967
968         lyxerr[Debug::GRAPHICS]
969                 << "filetools(getFormatFromContents)\n"
970                 << "\tCouldn't find a known format!\n";
971         return string();
972 }
973
974
975 /// check for zipped file
976 bool zippedFile(string const & name)
977 {
978         string const type = getFormatFromContents(name);
979         if (contains("gzip zip compress", type) && !type.empty())
980                 return true;
981         return false;
982 }
983
984
985 string const unzippedFileName(string const & zipped_file)
986 {
987         string const ext = GetExtension(zipped_file);
988         if (ext == "gz" || ext == "z" || ext == "Z")
989                 return ChangeExtension(zipped_file, string());
990         return "unzipped_" + zipped_file;
991 }
992
993
994 string const unzipFile(string const & zipped_file, string const & unzipped_file)
995 {
996         string const tempfile = unzipped_file.empty() ?
997                 unzippedFileName(zipped_file) : unzipped_file;
998         // Run gunzip
999         string const command = "gunzip -c " + zipped_file + " > " + tempfile;
1000         Systemcall one;
1001         one.startscript(Systemcall::Wait, command);
1002         // test that command was executed successfully (anon)
1003         // yes, please do. (Lgb)
1004         return tempfile;
1005 }
1006
1007
1008 string const MakeDisplayPath(string const & path, unsigned int threshold)
1009 {
1010         string str = path;
1011
1012         string const home(package().home_dir());
1013
1014         // replace /home/blah with ~/
1015         if (prefixIs(str, home))
1016                 str = subst(str, home, "~");
1017
1018         if (str.length() <= threshold)
1019                 return os::external_path(str);
1020
1021         string const prefix = ".../";
1022         string temp;
1023
1024         while (str.length() > threshold)
1025                 str = split(str, temp, '/');
1026
1027         // Did we shorten everything away?
1028         if (str.empty()) {
1029                 // Yes, filename itself is too long.
1030                 // Pick the start and the end of the filename.
1031                 str = OnlyFilename(path);
1032                 string const head = str.substr(0, threshold / 2 - 3);
1033
1034                 string::size_type len = str.length();
1035                 string const tail =
1036                         str.substr(len - threshold / 2 - 2, len - 1);
1037                 str = head + "..." + tail;
1038         }
1039
1040         return os::external_path(prefix + str);
1041 }
1042
1043
1044 bool LyXReadLink(string const & file, string & link, bool resolve)
1045 {
1046 #ifdef HAVE_READLINK
1047         char linkbuffer[512];
1048         // Should be PATH_MAX but that needs autconf support
1049         int const nRead = ::readlink(file.c_str(),
1050                                      linkbuffer, sizeof(linkbuffer) - 1);
1051         if (nRead <= 0)
1052                 return false;
1053         linkbuffer[nRead] = '\0'; // terminator
1054         if (resolve)
1055                 link = MakeAbsPath(linkbuffer, OnlyPath(file));
1056         else
1057                 link = linkbuffer;
1058         return true;
1059 #else
1060         return false;
1061 #endif
1062 }
1063
1064
1065 cmd_ret const RunCommand(string const & cmd)
1066 {
1067         // FIXME: replace all calls to RunCommand with ForkedCall
1068         // (if the output is not needed) or the code in ispell.C
1069         // (if the output is needed).
1070
1071         // One question is if we should use popen or
1072         // create our own popen based on fork, exec, pipe
1073         // of course the best would be to have a
1074         // pstream (process stream), with the
1075         // variants ipstream, opstream
1076
1077 #if defined (HAVE_POPEN)
1078         FILE * inf = ::popen(cmd.c_str(), os::popen_read_mode());
1079 #elif defined (HAVE__POPEN)
1080         FILE * inf = ::_popen(cmd.c_str(), os::popen_read_mode());
1081 #else
1082 #error No popen() function.
1083 #endif
1084
1085         // (Claus Hentschel) Check if popen was succesful ;-)
1086         if (!inf) {
1087                 return make_pair(-1, string());
1088                 lyxerr << "RunCommand:: could not start child process" << endl;
1089                 }
1090
1091         string ret;
1092         int c = fgetc(inf);
1093         while (c != EOF) {
1094                 ret += static_cast<char>(c);
1095                 c = fgetc(inf);
1096         }
1097
1098 #if defined (HAVE_PCLOSE)
1099         int const pret = pclose(inf);
1100 #elif defined (HAVE__PCLOSE)
1101         int const pret = _pclose(inf);
1102 #else
1103 #error No pclose() function.
1104 #endif
1105
1106         if (pret == -1)
1107                 perror("RunCommand:: could not terminate child process");
1108
1109         return make_pair(pret, ret);
1110 }
1111
1112
1113 string const findtexfile(string const & fil, string const & /*format*/)
1114 {
1115         /* There is no problem to extend this function too use other
1116            methods to look for files. It could be setup to look
1117            in environment paths and also if wanted as a last resort
1118            to a recursive find. One of the easier extensions would
1119            perhaps be to use the LyX file lookup methods. But! I am
1120            going to implement this until I see some demand for it.
1121            Lgb
1122         */
1123
1124         // If the file can be found directly, we just return a
1125         // absolute path version of it.
1126         if (fs::exists(fil))
1127                 return MakeAbsPath(fil);
1128
1129         // No we try to find it using kpsewhich.
1130         // It seems from the kpsewhich manual page that it is safe to use
1131         // kpsewhich without --format: "When the --format option is not
1132         // given, the search path used when looking for a file is inferred
1133         // from the name given, by looking for a known extension. If no
1134         // known extension is found, the search path for TeX source files
1135         // is used."
1136         // However, we want to take advantage of the format sine almost all
1137         // the different formats has environment variables that can be used
1138         // to controll which paths to search. f.ex. bib looks in
1139         // BIBINPUTS and TEXBIB. Small list follows:
1140         // bib - BIBINPUTS, TEXBIB
1141         // bst - BSTINPUTS
1142         // graphic/figure - TEXPICTS, TEXINPUTS
1143         // ist - TEXINDEXSTYLE, INDEXSTYLE
1144         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1145         // tex - TEXINPUTS
1146         // tfm - TFMFONTS, TEXFONTS
1147         // This means that to use kpsewhich in the best possible way we
1148         // should help it by setting additional path in the approp. envir.var.
1149         string const kpsecmd = "kpsewhich " + fil;
1150
1151         cmd_ret const c = RunCommand(kpsecmd);
1152
1153         lyxerr[Debug::LATEX] << "kpse status = " << c.first << '\n'
1154                  << "kpse result = `" << rtrim(c.second, "\n")
1155                  << '\'' << endl;
1156         if (c.first != -1)
1157                 return os::internal_path(rtrim(c.second, "\n\r"));
1158         else
1159                 return string();
1160 }
1161
1162
1163 void removeAutosaveFile(string const & filename)
1164 {
1165         string a = OnlyPath(filename);
1166         a += '#';
1167         a += OnlyFilename(filename);
1168         a += '#';
1169         if (fs::exists(a))
1170                 unlink(a);
1171 }
1172
1173
1174 void readBB_lyxerrMessage(string const & file, bool & zipped,
1175         string const & message)
1176 {
1177         lyxerr[Debug::GRAPHICS] << "[readBB_from_PSFile] "
1178                 << message << std::endl;
1179 #ifdef WITH_WARNINGS
1180 #warning Why is this func deleting a file? (Lgb)
1181 #endif
1182         if (zipped)
1183                 unlink(file);
1184 }
1185
1186
1187 string const readBB_from_PSFile(string const & file)
1188 {
1189         // in a (e)ps-file it's an entry like %%BoundingBox:23 45 321 345
1190         // It seems that every command in the header has an own line,
1191         // getline() should work for all files.
1192         // On the other hand some plot programs write the bb at the
1193         // end of the file. Than we have in the header:
1194         // %%BoundingBox: (atend)
1195         // In this case we must check the end.
1196         bool zipped = zippedFile(file);
1197         string const file_ = zipped ?
1198                 string(unzipFile(file)) : string(file);
1199         string const format = getFormatFromContents(file_);
1200
1201         if (format != "eps" && format != "ps") {
1202                 readBB_lyxerrMessage(file_, zipped,"no(e)ps-format");
1203                 return string();
1204         }
1205
1206         std::ifstream is(file_.c_str());
1207         while (is) {
1208                 string s;
1209                 getline(is,s);
1210                 if (contains(s,"%%BoundingBox:") && !contains(s,"atend")) {
1211                         string const bb = ltrim(s.substr(14));
1212                         readBB_lyxerrMessage(file_, zipped, bb);
1213                         return bb;
1214                 }
1215         }
1216         readBB_lyxerrMessage(file_, zipped, "no bb found");
1217         return string();
1218 }
1219
1220
1221 int compare_timestamps(string const & file1, string const & file2)
1222 {
1223         BOOST_ASSERT(AbsolutePath(file1) && AbsolutePath(file2));
1224
1225         // If the original is newer than the copy, then copy the original
1226         // to the new directory.
1227
1228         int cmp = 0;
1229         if (fs::exists(file1) && fs::exists(file2)) {
1230                 double const tmp = difftime(fs::last_write_time(file1),
1231                                             fs::last_write_time(file2));
1232                 if (tmp != 0)
1233                         cmp = tmp > 0 ? 1 : -1;
1234
1235         } else if (fs::exists(file1)) {
1236                 cmp = 1;
1237         } else if (fs::exists(file2)) {
1238                 cmp = -1;
1239         }
1240
1241         return cmp;
1242 }
1243
1244 } //namespace support
1245 } // namespace lyx