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