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