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