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