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