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