]> git.lyx.org Git - lyx.git/blob - src/support/filetools.C
various changes
[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 "filetools.h"
31 #include "LSubstring.h"
32 #include "lyx_gui_misc.h"
33 #include "FileInfo.h"
34 #include "support/path.h"        // I know it's OS/2 specific (SMiyata)
35 #include "gettext.h"
36 #include "lyxlib.h"
37 #include "os.h"
38
39 // Which part of this is still necessary? (JMarc).
40 #if HAVE_DIRENT_H
41 # include <dirent.h>
42 # define NAMLEN(dirent) strlen((dirent)->d_name)
43 #else
44 # define dirent direct
45 # define NAMLEN(dirent) (dirent)->d_namlen
46 # if HAVE_SYS_NDIR_H
47 #  include <sys/ndir.h>
48 # endif
49 # if HAVE_SYS_DIR_H
50 #  include <sys/dir.h>
51 # endif
52 # if HAVE_NDIR_H
53 #  include <ndir.h>
54 # endif
55 #endif
56
57 using std::make_pair;
58 using std::pair;
59 using std::endl;
60 using std::ifstream;
61 using std::vector;
62
63 extern string system_lyxdir;
64 extern string build_lyxdir;
65 extern string user_lyxdir;
66 extern string system_tempdir;
67
68
69 bool IsLyXFilename(string const & filename)
70 {
71         return suffixIs(filename, ".lyx");
72 }
73
74
75 bool IsSGMLFilename(string const & filename)
76 {
77         return suffixIs(filename, ".sgml");
78 }
79
80
81 // Substitutes spaces with underscores in filename (and path)
82 string const MakeLatexName(string const & file)
83 {
84         string name = OnlyFilename(file);
85         string const path = OnlyPath(file);
86         
87         for (string::size_type i = 0; i < name.length(); ++i) {
88                 name[i] &= 0x7f; // set 8th bit to 0
89         };
90
91         // ok so we scan through the string twice, but who cares.
92         string const keep("abcdefghijklmnopqrstuvwxyz"
93                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
94                 "@!\"'()*+,-./0123456789:;<=>?[]`|");
95         
96         string::size_type pos = 0;
97         while ((pos = name.find_first_not_of(keep, pos)) != string::npos) {
98                 name[pos++] = '_';
99         }
100         return AddName(path, name);
101 }
102
103
104 // Substitutes spaces with underscores in filename (and path)
105 string const QuoteName(string const & name)
106 {
107         return (os::shell() == os::UNIX) ?
108                 "\'" + name + "\'":
109                 "\"" + name + "\"";
110 }
111
112
113 // Is a file readable ?
114 bool IsFileReadable (string const & path)
115 {
116         FileInfo file(path);
117         if (file.isOK() && file.isRegular() && file.readable())
118                 return true;
119         else
120                 return false;
121 }
122
123
124 // Is a file read_only?
125 // return 1 read-write
126 //        0 read_only
127 //       -1 error (doesn't exist, no access, anything else) 
128 int IsFileWriteable (string const & path)
129 {
130         FileInfo fi(path);
131         if (fi.access(FileInfo::wperm|FileInfo::rperm)) // read-write
132                 return 1;
133         if (fi.readable()) // read-only
134                 return 0;
135         return -1; // everything else.
136 }
137
138
139 //returns 1: dir writeable
140 //        0: not writeable
141 //       -1: error- couldn't find out
142 int IsDirWriteable (string const & path)
143 {
144         string const tmpfl(lyx::tempName(path, "lyxwritetest"));
145         // We must unlink the tmpfl.
146         lyx::unlink(tmpfl);
147         
148         if (tmpfl.empty()) {
149                 WriteFSAlert(_("LyX Internal Error!"), 
150                              _("Could not test if directory is writeable"));
151                 return -1;
152         } else {
153                 FileInfo fi(path);
154                 if (fi.writable()) return 1;
155                 return 0;
156         }
157 }
158
159
160 // Uses a string of paths separated by ";"s to find a file to open.
161 // Can't cope with pathnames with a ';' in them. Returns full path to file.
162 // If path entry begins with $$LyX/, use system_lyxdir
163 // If path entry begins with $$User/, use user_lyxdir
164 // Example: "$$User/doc;$$LyX/doc"
165 string const FileOpenSearch (string const & path, string const & name, 
166                              string const & ext)
167 {
168         string real_file, path_element;
169         bool notfound = true;
170         string tmppath = split(path, path_element, ';');
171         
172         while (notfound && !path_element.empty()) {
173                 path_element = os::slashify_path(path_element);
174                 if (!suffixIs(path_element, '/'))
175                         path_element+= '/';
176                 path_element = subst(path_element, "$$LyX", system_lyxdir);
177                 path_element = subst(path_element, "$$User", user_lyxdir);
178                 
179                 real_file = FileSearch(path_element, name, ext);
180                 
181                 if (real_file.empty()) {
182                         do {
183                                 tmppath = split(tmppath, path_element, ';');
184                         } while(!tmppath.empty() && path_element.empty());
185                 } else {
186                         notfound = false;
187                 }
188         }
189 #ifdef __EMX__
190         if (ext.empty() && notfound) {
191                 real_file = FileOpenSearch(path, name, "exe");
192                 if (notfound) real_file = FileOpenSearch(path, name, "cmd");
193         }
194 #endif
195         return real_file;
196 }
197
198
199 /// Returns a vector of all files in directory dir having extension ext.
200 vector<string> const DirList(string const & dir, string const & ext)
201 {
202         // This is a non-error checking C/system implementation
203         string extension(ext);
204         if (!extension.empty() && extension[0] != '.')
205                 extension.insert(0, ".");
206         vector<string> dirlist;
207         DIR * dirp = ::opendir(dir.c_str());
208         dirent * dire;
209         while ((dire = ::readdir(dirp))) {
210                 string const fil = dire->d_name;
211                 if (suffixIs(fil, extension)) {
212                         dirlist.push_back(fil);
213                 }
214         }
215         ::closedir(dirp);
216         return dirlist;
217         /* I would have prefered to take a vector<string>& as parameter so
218            that we could avoid the copy of the vector when returning.
219            Then we would use:
220            dirlist.swap(argvec);
221            to avoid the copy. (Lgb)
222         */
223         /* A C++ implementaion will look like this:
224            string extension(ext);
225            if (extension[0] != '.') extension.insert(0, ".");
226            vector<string> dirlist;
227            directory_iterator dit("dir");
228            while (dit != directory_iterator()) {
229                    string fil = (*dit).filename;
230                    if (prefixIs(fil, extension)) {
231                            dirlist.push_back(fil);
232                    }
233                    ++dit;
234            }
235            dirlist.swap(argvec);
236            return;
237         */
238 }
239
240
241 // Returns the real name of file name in directory path, with optional
242 // extension ext.  
243 string const FileSearch(string const & path, string const & name, 
244                         string const & ext)
245 {
246         // if `name' is an absolute path, we ignore the setting of `path'
247         // Expand Environmentvariables in 'name'
248         string const tmpname = ReplaceEnvironmentPath(name);
249         string fullname = MakeAbsPath(tmpname, path);
250         // search first without extension, then with it.
251         if (IsFileReadable(fullname))
252                 return fullname;
253         else if (ext.empty()) 
254                 return string();
255         else { // Is it not more reasonable to use ChangeExtension()? (SMiyata)
256                 fullname += '.';
257                 fullname += ext;
258                 if (IsFileReadable(fullname))
259                         return fullname;
260                 else 
261                         return string();
262         }
263 }
264
265
266 // Search the file name.ext in the subdirectory dir of
267 //   1) user_lyxdir
268 //   2) build_lyxdir (if not empty)
269 //   3) system_lyxdir
270 string const LibFileSearch(string const & dir, string const & name, 
271                            string const & ext)
272 {
273         string fullname = FileSearch(AddPath(user_lyxdir, dir),
274                                      name, ext); 
275         if (!fullname.empty())
276                 return fullname;
277         
278         if (!build_lyxdir.empty()) 
279                 fullname = FileSearch(AddPath(build_lyxdir, dir), 
280                                       name, ext);
281         if (!fullname.empty())
282                 return fullname;
283         
284         return FileSearch(AddPath(system_lyxdir, dir), name, ext);
285 }
286
287
288 string const
289 i18nLibFileSearch(string const & dir, string const & name, 
290                   string const & ext)
291 {
292         string const lang = token(string(GetEnv("LANG")), '_', 0);
293         
294         if (lang.empty() || lang == "C")
295                 return LibFileSearch(dir, name, ext);
296         else {
297                 string const tmp = LibFileSearch(dir, lang + '_' + name,
298                                                  ext);
299                 if (!tmp.empty())
300                         return tmp;
301                 else
302                         return LibFileSearch(dir, name, ext);
303         }
304 }
305
306
307 string const GetEnv(string const & envname)
308 {
309         // f.ex. what about error checking?
310         char const * const ch = getenv(envname.c_str());
311         string const envstr = !ch ? "" : ch;
312         return envstr;
313 }
314
315
316 string const GetEnvPath(string const & name)
317 {
318 #ifndef __EMX__
319         string const pathlist = subst(GetEnv(name), ':', ';');
320 #else
321         string const pathlist = os::slashify_path(GetEnv(name));
322 #endif
323         return strip(pathlist, ';');
324 }
325
326
327 bool PutEnv(string const & envstr)
328 {
329         // CHECK Look at and fix this.
330         // f.ex. what about error checking?
331
332 #if HAVE_PUTENV
333         // this leaks, but what can we do about it?
334         //   Is doing a getenv() and a free() of the older value 
335         //   a good idea? (JMarc)
336         // Actually we don't have to leak...calling putenv like this
337         // should be enough: ... and this is obviously not enough if putenv
338         // does not make a copy of the string. It is also not very wise to
339         // put a string on the free store. If we have to leak we should do it
340         // like this:
341         char * leaker = new char[envstr.length() + 1];
342         envstr.copy(leaker, envstr.length());
343         leaker[envstr.length()] = '\0';
344         int const retval = lyx::putenv(leaker);
345
346         // If putenv does not make a copy of the char const * this
347         // is very dangerous. OTOH if it does take a copy this is the
348         // best solution.
349         // The  only implementation of putenv that I have seen does not
350         // allocate memory. _And_ after testing the putenv in glibc it
351         // seems that we need to make a copy of the string contents.
352         // I will enable the above.
353         //int retval = lyx::putenv(envstr.c_str());
354 #else
355 #ifdef HAVE_SETENV 
356         string varname;
357         string const str = envstr.split(varname,'=');
358         int const retval = ::setenv(varname.c_str(), str.c_str(), true);
359 #else
360         // No environment setting function. Can this happen?
361         int const retval = 1; //return an error condition.
362 #endif
363 #endif
364         return retval == 0;
365 }
366
367
368 bool PutEnvPath(string const & envstr)
369 {
370         return PutEnv(envstr);
371 }
372
373
374 namespace {
375
376 int DeleteAllFilesInDir (string const & path)
377 {
378         // I have decided that we will be using parts from the boost
379         // library. Check out http://www.boost.org/
380         // For directory access we will then use the directory_iterator.
381         // Then the code will be something like:
382         // directory_iterator dit(path);
383         // directory_iterator dend;
384         // if (dit == dend) {
385         //         WriteFSAlert(_("Error! Cannot open directory:"), path);
386         //         return -1;
387         // }
388         // for (; dit != dend; ++dit) {
389         //         string filename(*dit);
390         //         if (filename == "." || filename == "..")
391         //                 continue;
392         //         string unlinkpath(AddName(path, filename));
393         //         if (lyx::unlink(unlinkpath))
394         //                 WriteFSAlert(_("Error! Could not remove file:"),
395         //                              unlinkpath);
396         // }
397         // return 0;
398         DIR * dir = ::opendir(path.c_str());
399         if (!dir) {
400                 WriteFSAlert (_("Error! Cannot open directory:"), path);
401                 return -1;
402         }
403         struct dirent * de;
404         int return_value = 0;
405         while ((de = readdir(dir))) {
406                 string const temp = de->d_name;
407                 if (temp == "." || temp == "..") 
408                         continue;
409                 string const unlinkpath = AddName (path, temp);
410
411                 lyxerr.debug() << "Deleting file: " << unlinkpath << endl;
412
413                 bool deleted = true;
414                 if (FileInfo(unlinkpath).isDir())
415                         deleted = (DeleteAllFilesInDir(unlinkpath) == 0);
416                 deleted &= (lyx::unlink(unlinkpath) == 0);
417                 if (!deleted) {
418                         WriteFSAlert (_("Error! Could not remove file:"), 
419                                       unlinkpath);
420                         return_value = -1;
421                 }
422         }
423         closedir(dir);
424         return return_value;
425 }
426
427
428 string const CreateTmpDir(string const & tempdir, string const & mask)
429 {
430         lyxerr[Debug::FILES]
431                 << "CreateTmpDir: tempdir=`" << tempdir << "'\n"
432                 << "CreateTmpDir:    mask=`" << mask << "'" << endl;
433         
434         string const tmpfl(lyx::tempName(tempdir, mask));
435         // lyx::tempName actually creates a file to make sure that it
436         // stays unique. So we have to delete it before we can create
437         // a dir with the same name. Note also that we are not thread
438         // safe because of the gap between unlink and mkdir. (Lgb)
439         lyx::unlink(tmpfl.c_str());
440         
441         if (tmpfl.empty() || lyx::mkdir(tmpfl, 0700)) {
442                 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
443                              tempdir);
444                 return string();
445         }
446         return MakeAbsPath(tmpfl);
447 }
448
449
450 int DestroyTmpDir(string const & tmpdir, bool Allfiles)
451 {
452 #ifdef __EMX__
453         Path p(user_lyxdir);
454 #endif
455         if (Allfiles && DeleteAllFilesInDir(tmpdir)) return -1;
456         if (lyx::rmdir(tmpdir)) { 
457                 WriteFSAlert(_("Error! Couldn't delete temporary directory:"), 
458                              tmpdir);
459                 return -1;
460         }
461         return 0; 
462 }
463
464 } // namespace anon
465
466
467 string const CreateBufferTmpDir(string const & pathfor)
468 {
469         static int count;
470         static string const tmpdir(pathfor.empty() ? os::getTmpDir() : pathfor);
471         // We are in our own directory.  Why bother to mangle name?
472         // In fact I wrote this code to circumvent a problematic behaviour (bug?)
473         // of EMX mkstemp().
474         string const tmpfl = tmpdir + "/lyx_tmpbuf" + tostr(count++);
475         if (lyx::mkdir(tmpfl, 0777)) {
476                 WriteFSAlert(_("Error! Couldn't create temporary directory:"),
477                              tmpdir);
478                 return string();
479         }
480         return tmpfl;
481 }
482
483
484 int DestroyBufferTmpDir(string const & tmpdir)
485 {
486         return DestroyTmpDir(tmpdir, true);
487 }
488
489
490 string const CreateLyXTmpDir(string const & deflt)
491 {
492         if ((!deflt.empty()) && (deflt  != "/tmp")) {
493                 if (lyx::mkdir(deflt, 0777)) {
494 #ifdef __EMX__
495                         Path p(user_lyxdir);
496 #endif
497                         string const t(CreateTmpDir(deflt, "lyx_tmpdir"));
498                         return t;
499                 } else
500                         return deflt;
501         } else {
502 #ifdef __EMX__
503                 Path p(user_lyxdir);
504 #endif
505                 string const t(CreateTmpDir("/tmp", "lyx_tmpdir"));
506                 return t;
507         }
508 }
509
510
511 int DestroyLyXTmpDir(string const & tmpdir)
512 {
513        return DestroyTmpDir (tmpdir, false); // Why false?
514 }
515
516
517 // Creates directory. Returns true if succesfull
518 bool createDirectory(string const & path, int permission)
519 {
520         string temp(strip(os::slashify_path(path), '/'));
521
522         if (temp.empty()) {
523                 WriteAlert(_("Internal error!"),
524                            _("Call to createDirectory with invalid name"));
525                 return false;
526         }
527
528         if (lyx::mkdir(temp, permission)) {
529                 WriteFSAlert (_("Error! Couldn't create directory:"), temp);
530                 return false;
531         }
532         return true;
533 }
534
535
536 // Strip filename from path name
537 string const OnlyPath(string const & Filename)
538 {
539         // If empty filename, return empty
540         if (Filename.empty()) return Filename;
541
542         // Find last / or start of filename
543         string::size_type j = Filename.rfind('/');
544         if (j == string::npos)
545                 return "./";
546         return Filename.substr(0, j + 1);
547 }
548
549
550 // Convert relative path into absolute path based on a basepath.
551 // If relpath is absolute, just use that.
552 // If basepath is empty, use CWD as base.
553 string const MakeAbsPath(string const & RelPath, string const & BasePath)
554 {
555         // checks for already absolute path
556         if (AbsolutePath(RelPath))
557                 return RelPath;
558
559         // Copies given paths
560         string TempRel(os::slashify_path(RelPath));
561         // Since TempRel is NOT absolute, we can safely replace "//" with "/"
562         TempRel = subst(TempRel, "//", "/");
563
564         string TempBase;
565
566         if (AbsolutePath(BasePath))
567                 TempBase = BasePath;
568         else
569                 TempBase = AddPath(lyx::getcwd(), BasePath);
570         
571         // Handle /./ at the end of the path
572         while(suffixIs(TempBase, "/./"))
573                 TempBase.erase(TempBase.length() - 2);
574
575         // processes relative path
576         string RTemp(TempRel);
577         string Temp;
578
579         while (!RTemp.empty()) {
580                 // Split by next /
581                 RTemp = split(RTemp, Temp, '/');
582                 
583                 if (Temp == ".") continue;
584                 if (Temp == "..") {
585                         // Remove one level of TempBase
586                         string::difference_type i = TempBase.length() - 2;
587 #ifndef __EMX__
588                         if (i < 0) i = 0;
589                         while (i > 0 && TempBase[i] != '/') --i;
590                         if (i > 0)
591 #else
592                         if (i < 2) i = 2;
593                         while (i > 2 && TempBase[i] != '/') --i;
594                         if (i > 2)
595 #endif
596                                 TempBase.erase(i, string::npos);
597                         else
598                                 TempBase += '/';
599                 } else if (Temp.empty() && !RTemp.empty()) {
600                                 TempBase = os::current_root() + RTemp;
601                                 RTemp.erase();
602                 } else {
603                         // Add this piece to TempBase
604                         if (!suffixIs(TempBase, '/'))
605                                 TempBase += '/';
606                         TempBase += Temp;
607                 }
608         }
609
610         // returns absolute path
611         return os::slashify_path(TempBase);
612 }
613
614
615 // Correctly append filename to the pathname.
616 // If pathname is '.', then don't use pathname.
617 // Chops any path of filename.
618 string const AddName(string const & path, string const & fname)
619 {
620         // Get basename
621         string const basename(OnlyFilename(fname));
622
623         string buf;
624
625         if (path != "." && path != "./" && !path.empty()) {
626                 buf = os::slashify_path(path);
627                 if (!suffixIs(path, '/'))
628                         buf += '/';
629         }
630
631         return buf + basename;
632 }
633
634
635 // Strips path from filename
636 string const OnlyFilename(string const & fname)
637 {
638         if (fname.empty())
639                 return fname;
640
641         string::size_type j = fname.rfind('/');
642         if (j == string::npos) // no '/' in fname
643                 return fname;
644
645         // Strip to basename
646         return fname.substr(j + 1);
647 }
648
649
650 // Is a filename/path absolute?
651 bool AbsolutePath(string const & path)
652 {
653 #ifndef __EMX__
654         return (!path.empty() && path[0] == '/');
655 #else
656         return (!path.empty() && isalpha(static_cast<unsigned char>(path[0])) && path.length() > 1 && path[1] == ':');
657 #endif
658 }
659
660
661 // Create absolute path. If impossible, don't do anything
662 // Supports ./ and ~/. Later we can add support for ~logname/. (Asger)
663 string const ExpandPath(string const & path)
664 {
665         // checks for already absolute path
666         string RTemp(ReplaceEnvironmentPath(path));
667         if (AbsolutePath(RTemp))
668                 return RTemp;
669
670         string Temp;
671         string const copy(RTemp);
672
673         // Split by next /
674         RTemp = split(RTemp, Temp, '/');
675
676         if (Temp == ".") {
677                 return lyx::getcwd() /*GetCWD()*/ + '/' + RTemp;
678         } else if (Temp == "~") {
679                 return GetEnvPath("HOME") + '/' + RTemp;
680         } else if (Temp == "..") {
681                 return MakeAbsPath(copy);
682         } else
683                 // Don't know how to handle this
684                 return copy;
685 }
686
687
688 // Normalize a path
689 // Constracts path/../path
690 // Can't handle "../../" or "/../" (Asger)
691 string const NormalizePath(string const & path)
692 {
693         string TempBase;
694         string RTemp;
695         string Temp;
696
697         if (AbsolutePath(path))
698                 RTemp = path;
699         else
700                 // Make implicit current directory explicit
701                 RTemp = "./" +path;
702
703         while (!RTemp.empty()) {
704                 // Split by next /
705                 RTemp = split(RTemp, Temp, '/');
706                 
707                 if (Temp == ".") {
708                         TempBase = "./";
709                 } else if (Temp == "..") {
710                         // Remove one level of TempBase
711                         string::difference_type i = TempBase.length() - 2;
712                         while (i > 0 && TempBase[i] != '/')
713                                 --i;
714                         if (i >= 0 && TempBase[i] == '/')
715                                 TempBase.erase(i + 1, string::npos);
716                         else
717                                 TempBase = "../";
718                 } else {
719                         TempBase += Temp + '/';
720                 }
721         }
722
723         // returns absolute path
724         return TempBase;        
725 }
726
727
728 string const GetFileContents(string const & fname)
729 {
730         FileInfo finfo(fname);
731         if (finfo.exist()) {
732                 ifstream ifs(fname.c_str());
733                 std::ostringstream ofs;
734                 if (ifs && ofs) {
735                         ofs << ifs.rdbuf();
736                         ifs.close();
737                         return ofs.str().c_str();
738                 }
739         }
740         lyxerr << "LyX was not able to read file '" << fname << "'" << endl;
741         return string();
742 }
743
744
745 //
746 // Search ${...} as Variable-Name inside the string and replace it with
747 // the denoted environmentvariable
748 // Allow Variables according to 
749 //  variable :=  '$' '{' [A-Za-z_]{[A-Za-z_0-9]*} '}'
750 //
751
752 string const ReplaceEnvironmentPath(string const & path)
753 {
754 // 
755 // CompareChar: Environmentvariables starts with this character
756 // PathChar:    Next path component start with this character
757 // while CompareChar found do:
758 //       Split String with PathChar
759 //       Search Environmentvariable
760 //       if found: Replace Strings
761 //
762         char const CompareChar = '$';
763         char const FirstChar = '{'; 
764         char const EndChar = '}'; 
765         char const UnderscoreChar = '_'; 
766         string EndString; EndString += EndChar;
767         string FirstString; FirstString += FirstChar;
768         string CompareString; CompareString += CompareChar;
769         string const RegExp("*}*"); // Exist EndChar inside a String?
770
771 // first: Search for a '$' - Sign.
772         //string copy(path);
773         string result1; //(copy);    // for split-calls
774         string result0 = split(path, result1, CompareChar);
775         while (!result0.empty()) {
776                 string copy1(result0); // contains String after $
777                 
778                 // Check, if there is an EndChar inside original String.
779                 
780                 if (!regexMatch(copy1, RegExp)) {
781                         // No EndChar inside. So we are finished
782                         result1 += CompareString + result0;
783                         result0.erase();
784                         continue;
785                 }
786
787                 string res1;
788                 string res0 = split(copy1, res1, EndChar);
789                 // Now res1 holds the environmentvariable
790                 // First, check, if Contents is ok.
791                 if (res1.empty()) { // No environmentvariable. Continue Loop.
792                         result1 += CompareString + FirstString;
793                         result0  = res0;
794                         continue;
795                 }
796                 // check contents of res1
797                 char const * res1_contents = res1.c_str();
798                 if (*res1_contents != FirstChar) {
799                         // Again No Environmentvariable
800                         result1 += CompareString;
801                         result0 = res0;
802                 }
803
804                 // Check for variable names
805                 // Situation ${} is detected as "No Environmentvariable"
806                 char const * cp1 = res1_contents + 1;
807                 bool result = isalpha(*cp1) || (*cp1 == UnderscoreChar);
808                 ++cp1;
809                 while (*cp1 && result) {
810                         result = isalnum(*cp1) || 
811                                 (*cp1 == UnderscoreChar); 
812                         ++cp1;
813                 }
814
815                 if (!result) {
816                         // no correct variable name
817                         result1 += CompareString + res1 + EndString;
818                         result0  = split(res0, res1, CompareChar);
819                         result1 += res1;
820                         continue;
821                 }
822             
823                 string env(GetEnv(res1_contents + 1));
824                 if (!env.empty()) {
825                         // Congratulations. Environmentvariable found
826                         result1 += env;
827                 } else {
828                         result1 += CompareString + res1 + EndString;
829                 }
830                 // Next $-Sign?
831                 result0  = split(res0, res1, CompareChar);
832                 result1 += res1;
833         } 
834         return result1;
835 }  // ReplaceEnvironmentPath
836
837
838 // Make relative path out of two absolute paths
839 string const MakeRelPath(string const & abspath, string const & basepath)
840 // Makes relative path out of absolute path. If it is deeper than basepath,
841 // it's easy. If basepath and abspath share something (they are all deeper
842 // than some directory), it'll be rendered using ..'s. If they are completely
843 // different, then the absolute path will be used as relative path.
844 {
845         string::size_type const abslen = abspath.length();
846         string::size_type const baselen = basepath.length();
847
848         string::size_type i = os::common_path(abspath, basepath);
849
850         if (i == 0) {
851                 // actually no match - cannot make it relative
852                 return abspath;
853         }
854
855         // Count how many dirs there are in basepath above match
856         // and append as many '..''s into relpath
857         string buf;
858         string::size_type j = i;
859         while (j < baselen) {
860                 if (basepath[j] == '/') {
861                         if (j + 1 == baselen) break;
862                         buf += "../";
863                 }
864                 ++j;
865         }
866
867         // Append relative stuff from common directory to abspath
868         if (abspath[i] == '/') ++i;
869         for (; i < abslen; ++i)
870                 buf += abspath[i];
871         // Remove trailing /
872         if (suffixIs(buf, '/'))
873                 buf.erase(buf.length() - 1);
874         // Substitute empty with .
875         if (buf.empty())
876                 buf = '.';
877         return buf;
878 }
879
880
881 // Append sub-directory(ies) to a path in an intelligent way
882 string const AddPath(string const & path, string const & path_2)
883 {
884         string buf;
885         string const path2 = os::slashify_path(path_2);
886
887         if (!path.empty() && path != "." && path != "./") {
888                 buf = os::slashify_path(path);
889                 if (path[path.length() - 1] != '/')
890                         buf += '/';
891         }
892
893         if (!path2.empty()) {
894                 string::size_type const p2start = path2.find_first_not_of('/');
895                 string::size_type const p2end = path2.find_last_not_of('/');
896                 string const tmp = path2.substr(p2start, p2end - p2start + 1);
897                 buf += tmp + '/';
898         }
899         return buf;
900 }
901
902
903 /* 
904  Change extension of oldname to extension.
905  Strips path off if no_path == true.
906  If no extension on oldname, just appends.
907  */
908 string const
909 ChangeExtension(string const & oldname, string const & extension)
910 {
911         string::size_type const last_slash = oldname.rfind('/');
912         string::size_type last_dot = oldname.rfind('.');
913         if (last_dot < last_slash && last_slash != string::npos)
914                 last_dot = string::npos;
915         
916         string ext;
917         // Make sure the extension starts with a dot
918         if (!extension.empty() && extension[0] != '.')
919                 ext= "." + extension;
920         else
921                 ext = extension;
922
923         return os::slashify_path(oldname.substr(0, last_dot) + ext);
924 }
925
926
927 /// Return the extension of the file (not including the .)
928 string const GetExtension(string const & name)
929 {
930         string::size_type const last_slash = name.rfind('/');
931         string::size_type const last_dot = name.rfind('.');
932         if (last_dot != string::npos &&
933             (last_slash == string::npos || last_dot > last_slash))
934                 return name.substr(last_dot + 1,
935                                    name.length() - (last_dot + 1));
936         else
937                 return string();
938 }
939
940
941 // Creates a nice compact path for displaying
942 string const
943 MakeDisplayPath (string const & path, unsigned int threshold)
944 {
945         string::size_type const l1 = path.length();
946
947         // First, we try a relative path compared to home
948         string const home(GetEnvPath("HOME"));
949         string relhome = MakeRelPath(path, home);
950
951         string::size_type l2 = relhome.length();
952
953         string prefix;
954
955         // If we backup from home or don't have a relative path,
956         // this try is no good
957         if (prefixIs(relhome, "../") || AbsolutePath(relhome)) {
958                 // relative path was no good, just use the original path
959                 relhome = path;
960                 l2 = l1;
961         } else {
962                 prefix = "~/";
963         }
964
965         // Is the path too long?
966         if (l2 > threshold) {
967                 // Yes, shortend it
968                 prefix += ".../";
969                 
970                 string temp;
971                 
972                 while (relhome.length() > threshold)
973                         relhome = split(relhome, temp, '/');
974
975                 // Did we shortend everything away?
976                 if (relhome.empty()) {
977                         // Yes, filename in itself is too long.
978                         // Pick the start and the end of the filename.
979                         relhome = OnlyFilename(path);
980                         string const head = relhome.substr(0, threshold/2 - 3);
981
982                         l2 = relhome.length();
983                         string const tail =
984                                 relhome.substr(l2 - threshold/2 - 2, l2 - 1);
985                         relhome = head + "..." + tail;
986                 }
987         }
988         return prefix + relhome;
989 }
990
991
992 bool LyXReadLink(string const & File, string & Link)
993 {
994         char LinkBuffer[512];
995         // Should be PATH_MAX but that needs autconf support
996         int const nRead = ::readlink(File.c_str(),
997                                      LinkBuffer, sizeof(LinkBuffer) - 1);
998         if (nRead <= 0)
999                 return false;
1000         LinkBuffer[nRead] = '\0'; // terminator
1001         Link = LinkBuffer;
1002         return true;
1003 }
1004
1005
1006 namespace {
1007
1008 typedef pair<int, string> cmdret;
1009
1010 cmdret const do_popen(string const & cmd)
1011 {
1012         // One question is if we should use popen or
1013         // create our own popen based on fork, exec, pipe
1014         // of course the best would be to have a
1015         // pstream (process stream), with the
1016         // variants ipstream, opstream
1017         FILE * inf = ::popen(cmd.c_str(), "r");
1018         string ret;
1019         int c = fgetc(inf);
1020         while (c != EOF) {
1021                 ret += static_cast<char>(c);
1022                 c = fgetc(inf);
1023         }
1024         int const pret = pclose(inf);
1025         return make_pair(pret, ret);
1026 }
1027
1028 } // namespace anon
1029
1030
1031 string const
1032 findtexfile(string const & fil, string const & /*format*/)
1033 {
1034         /* There is no problem to extend this function too use other
1035            methods to look for files. It could be setup to look
1036            in environment paths and also if wanted as a last resort
1037            to a recursive find. One of the easier extensions would
1038            perhaps be to use the LyX file lookup methods. But! I am
1039            going to implement this until I see some demand for it.
1040            Lgb
1041         */
1042         
1043         // If the file can be found directly, we just return a
1044         // absolute path version of it. 
1045         if (FileInfo(fil).exist())
1046                 return MakeAbsPath(fil);
1047
1048         // No we try to find it using kpsewhich.
1049         // It seems from the kpsewhich manual page that it is safe to use
1050         // kpsewhich without --format: "When the --format option is not
1051         // given, the search path used when looking for a file is inferred
1052         // from the name given, by looking for a known extension. If no
1053         // known extension is found, the search path for TeX source files
1054         // is used."
1055         // However, we want to take advantage of the format sine almost all
1056         // the different formats has environment variables that can be used
1057         // to controll which paths to search. f.ex. bib looks in
1058         // BIBINPUTS and TEXBIB. Small list follows:
1059         // bib - BIBINPUTS, TEXBIB
1060         // bst - BSTINPUTS
1061         // graphic/figure - TEXPICTS, TEXINPUTS
1062         // ist - TEXINDEXSTYLE, INDEXSTYLE
1063         // pk - PROGRAMFONTS, PKFONTS, TEXPKS, GLYPHFONTS, TEXFONTS
1064         // tex - TEXINPUTS
1065         // tfm - TFMFONTS, TEXFONTS
1066         // This means that to use kpsewhich in the best possible way we
1067         // should help it by setting additional path in the approp. envir.var.
1068         string const kpsecmd = "kpsewhich " + fil;
1069
1070         cmdret const c = do_popen(kpsecmd);
1071         
1072         lyxerr[Debug::LATEX] << "kpse status = " << c.first << "\n"
1073                              << "kpse result = `" << strip(c.second, '\n') 
1074                              << "'" << endl;
1075         return c.first != -1 ? strip(c.second, '\n') : string();
1076 }
1077
1078
1079 void removeAutosaveFile(string const & filename)
1080 {
1081         string a = OnlyPath(filename);
1082         a += '#';
1083         a += OnlyFilename(filename);
1084         a += '#';
1085         FileInfo const fileinfo(a);
1086         if (fileinfo.exist()) {
1087                 if (lyx::unlink(a) != 0) {
1088                         WriteFSAlert(_("Could not delete auto-save file!"), a);
1089                 }
1090         }
1091 }