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